Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 60 additions & 13 deletions .claude/skills/bear-observe/harness/tree.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,37 @@
/**
* Renders an observation log as a tree, naming the type that closes each scope.
*
* Usage: php tree.php [log.json] (default: the newest latest.json under var/log)
* Usage: php tree.php [log.json] [--full] (default: the newest latest.json under var/log)
*
* `stree` prints the closing context but not its type, and here the type carries the answer:
* `get` closed by `cache_hit` and `get` closed by `cache_miss` have the same context keys.
*
* `--full` disables the 60-char value truncation; without it, a cut value is marked
* `...(+N)` with the number of characters dropped, so it reads as cut rather than as a
* value that happened to end there.
*/

$argvList = $argv;
array_shift($argvList);

$full = false;
$positional = [];
foreach ((is_array($argvList) ? $argvList : []) as $arg) {
if ($arg === '--full') {
$full = true;
continue;
}

$positional[] = $arg;
}

function fail(string $message): never
{
fwrite(STDERR, "FAIL {$message}\n");
exit(1);
}

$file = $argvList[0] ?? null;
$file = $positional[0] ?? null;
if ($file === null) {
$found = glob(getcwd() . '/var/log/*/observe/latest.json') ?: [];
$found !== [] || fail('no var/log/*/observe/latest.json — run a request first, or pass a path');
Expand All @@ -34,37 +49,69 @@ function fail(string $message): never
is_array($log) || fail("not JSON: {$file}");

// Runs in the application's PHP, which may be a no-dev install without ext-mbstring.
function truncate(string $text): string
function truncate(string $text, bool $full): string
{
if ($full) {
return $text;
}

if (function_exists('mb_substr')) {
return mb_strlen($text) > 60 ? mb_substr($text, 0, 57) . '...' : $text;
$length = mb_strlen($text);

return $length > 60 ? mb_substr($text, 0, 57) . '...(+' . ($length - 57) . ')' : $text;
}

$length = strlen($text);

return $length > 60 ? substr($text, 0, 57) . '...(+' . ($length - 57) . ')' : $text;
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

// A value whose whole string is backslash-joined identifiers is a FQCN, not a URI, a JSON
// blob, or free text — none of those parse as this pattern.
function shortenFqcn(string $text): string
{
if (! str_contains($text, '\\') || preg_match('/^(\\\\?[A-Za-z_][A-Za-z0-9_]*)+$/', $text) !== 1) {
return $text;
}

$pos = strrpos($text, '\\');

return $pos === false ? $text : substr($text, $pos + 1);
}

function formatScalar(bool|int|float|string $value): string
{
if (is_bool($value)) {
return $value ? 'true' : 'false';
}

return strlen($text) > 60 ? substr($text, 0, 57) . '...' : $text;
return (string) $value;
}

/** @param array<string, mixed> $context */
function summarize(array $context): string
function summarize(array $context, bool $full): string
{
$parts = [];
foreach ($context as $key => $value) {
if ($key === 'timestamp' || $value === null || $value === []) {
continue;
}

$text = is_scalar($value) ? (string) $value : (string) json_encode($value, JSON_UNESCAPED_SLASHES);
$text = is_scalar($value) ? formatScalar($value) : (string) json_encode($value, JSON_UNESCAPED_SLASHES);
if ($key === 'body_ref') {
$text = basename($text);
} elseif (is_string($value)) {
$text = shortenFqcn($text);
}

$parts[] = $key . '=' . truncate($text);
$parts[] = $key . '=' . truncate($text, $full);
}

return implode(' ', $parts);
}

/** @param array<string, mixed> $node */
function render(array $node, string $indent = ''): void
function render(array $node, bool $full, string $indent = ''): void
{
// Events are recorded against the enclosing scope, so they belong under it, ahead of the
// scopes it went on to open.
Expand All @@ -80,12 +127,12 @@ function render(array $node, string $indent = ''): void
$next = $indent . ($i === $last ? ' ' : '│ ');
$close = $child['close'] ?? null;
$arrow = is_array($close) && is_string($close['type'] ?? null)
? ' → ' . $close['type'] . ' ' . summarize((array) ($close['context'] ?? []))
? ' → ' . $close['type'] . ' ' . summarize((array) ($close['context'] ?? []), $full)
: '';
echo $indent, $branch, $child['type'], ' ', summarize((array) ($child['context'] ?? [])), $arrow, "\n";
render($child, $next);
echo $indent, $branch, $child['type'], ' ', summarize((array) ($child['context'] ?? []), $full), $arrow, "\n";
render($child, $full, $next);
}
}

echo $file, "\n";
render($log);
render($log, $full);
50 changes: 25 additions & 25 deletions be/composer.json
Original file line number Diff line number Diff line change
@@ -1,28 +1,28 @@
{
"name": "my-vendor/be-mart-be",
"type": "library",
"license": "MIT",
"description": "BeMart Be — pure Be Framework domain (Input / Final / Semantic / Exception / Becoming / Reason) for the BeMart project.",
"require": {
"php": "^8.3",
"be-framework/be": "dev-semantic-logger-0.9 as 0.1.0",
"ray/input-query": "^1.0",
"ray/media-query": "^1.1"
},
"require-dev": {
"phpunit/phpunit": "^10.0"
},
"autoload": {
"psr-4": {
"MyVendor\\BeMart\\Be\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"MyVendor\\BeMart\\Be\\Tests\\": "tests/"
}
},
"config": {
"sort-packages": true
"name": "my-vendor/be-mart-be",
"type": "library",
"license": "MIT",
"description": "BeMart Be — pure Be Framework domain (Input / Final / Semantic / Exception / Becoming / Reason) for the BeMart project.",
"require": {
"php": "^8.3",
"be-framework/be": "0.x-dev as 0.1.0",
"ray/input-query": "^1.0",
"ray/media-query": "^1.1"
},
"require-dev": {
"phpunit/phpunit": "^10.0"
},
"autoload": {
"psr-4": {
"MyVendor\\BeMart\\Be\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"MyVendor\\BeMart\\Be\\Tests\\": "tests/"
}
},
"config": {
"sort-packages": true
}
}
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
],
"require": {
"php": "^8.3",
"be-framework/be": "dev-semantic-logger-0.9 as 0.1.0",
"be-framework/be": "0.x-dev as 0.1.0",
"bear/devtools": "dev-master",
"bear/package": "^1.14",
"bear/query-repository": "1.x-dev",
Expand Down
29 changes: 15 additions & 14 deletions composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading