@kontourai/flow-agents 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/ci.yml +110 -0
- package/CHANGELOG.md +24 -0
- package/build/src/cli/console-learning-projection.js +19 -2
- package/build/src/cli/effective-backlog-settings.js +18 -2
- package/build/src/cli/fixture-retirement-audit.js +19 -2
- package/build/src/cli/flow-kit.js +135 -5
- package/build/src/cli/init.js +19 -2
- package/build/src/cli/promote-workflow-artifact.js +19 -2
- package/build/src/cli/publish-change-helper.js +19 -2
- package/build/src/cli/pull-work-provider.js +19 -2
- package/build/src/cli/runtime-adapter.js +20 -2
- package/build/src/cli/usage-feedback.js +19 -2
- package/build/src/cli/utterance-check.js +19 -2
- package/build/src/cli/validate-hook-influence.js +19 -2
- package/build/src/cli/validate-source-tree.js +19 -2
- package/build/src/cli/veritas-governance.js +19 -2
- package/build/src/cli/workflow-artifact-cleanup-audit.js +19 -2
- package/build/src/runtime-adapters.js +56 -25
- package/build/src/tools/build-universal-bundles.js +19 -2
- package/build/src/tools/generate-context-map.js +19 -2
- package/build/src/tools/validate-package.js +19 -2
- package/build/src/tools/validate-source-tree.js +20 -3
- package/context/scripts/telemetry/console-presets.sh +1 -1
- package/docs/fixture-ownership.md +1 -1
- package/docs/kit-authoring-guide.md +20 -3
- package/evals/ci/run-baseline.sh +55 -8
- package/evals/integration/test_activate_npx_context.sh +134 -0
- package/evals/integration/test_flow_kit_install_git.sh +163 -0
- package/evals/integration/test_runtime_adapter_activation.sh +138 -17
- package/evals/run.sh +2 -0
- package/evals/static/test_console_presets.sh +49 -0
- package/package.json +1 -1
- package/scripts/telemetry/console-presets.sh +1 -1
- package/src/cli/console-learning-projection.ts +7 -1
- package/src/cli/effective-backlog-settings.ts +6 -1
- package/src/cli/fixture-retirement-audit.ts +7 -1
- package/src/cli/flow-kit.ts +123 -4
- package/src/cli/init.ts +7 -1
- package/src/cli/promote-workflow-artifact.ts +7 -1
- package/src/cli/publish-change-helper.ts +7 -1
- package/src/cli/pull-work-provider.ts +7 -1
- package/src/cli/runtime-adapter.ts +8 -1
- package/src/cli/usage-feedback.ts +7 -1
- package/src/cli/utterance-check.ts +7 -1
- package/src/cli/validate-hook-influence.ts +7 -1
- package/src/cli/validate-source-tree.ts +7 -1
- package/src/cli/veritas-governance.ts +7 -1
- package/src/cli/workflow-artifact-cleanup-audit.ts +7 -1
- package/src/runtime-adapters.ts +55 -27
- package/src/tools/build-universal-bundles.ts +7 -1
- package/src/tools/generate-context-map.ts +7 -1
- package/src/tools/validate-package.ts +7 -1
- package/src/tools/validate-source-tree.ts +8 -2
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# test_activate_npx_context.sh — Verify that activate exits 0 in an npx-like context
|
|
3
|
+
# where there is no kits/catalog.json at the source-root.
|
|
4
|
+
# Implements acceptance criteria for kontourai/flow-agents#57.
|
|
5
|
+
set -uo pipefail
|
|
6
|
+
|
|
7
|
+
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
8
|
+
source "$ROOT/evals/lib/node.sh"
|
|
9
|
+
|
|
10
|
+
errors=0
|
|
11
|
+
TMP_DIR="$(mktemp -d)"
|
|
12
|
+
trap 'rm -rf "$TMP_DIR"' EXIT
|
|
13
|
+
|
|
14
|
+
pass() { echo " ✓ $1"; }
|
|
15
|
+
fail() { echo " ✗ $1"; errors=$((errors + 1)); }
|
|
16
|
+
|
|
17
|
+
CLI="$ROOT/scripts/flow-kit.js"
|
|
18
|
+
MIXED_SRC="$ROOT/evals/fixtures/flow-kit-repository/mixed-runtime-kit"
|
|
19
|
+
|
|
20
|
+
echo "=== activate npx-context Checks (Issue #57) ==="
|
|
21
|
+
|
|
22
|
+
# Simulate the npx context: an empty source-root (no kits/catalog.json present).
|
|
23
|
+
NPX_SIMULATED_ROOT="$TMP_DIR/simulated-npx-root"
|
|
24
|
+
mkdir -p "$NPX_SIMULATED_ROOT"
|
|
25
|
+
DEST="$TMP_DIR/dest"
|
|
26
|
+
mkdir -p "$DEST"
|
|
27
|
+
|
|
28
|
+
# Install a kit into the destination workspace first.
|
|
29
|
+
install_out="$TMP_DIR/install.out"
|
|
30
|
+
if flow_agents_node "$CLI" install-local "$MIXED_SRC" --dest "$DEST" >"$install_out" 2>&1; then
|
|
31
|
+
pass "mixed-runtime-kit installs into workspace"
|
|
32
|
+
else
|
|
33
|
+
fail "install failed (prerequisite step)"
|
|
34
|
+
sed -n '1,80p' "$install_out"
|
|
35
|
+
fi
|
|
36
|
+
|
|
37
|
+
# --- Test 1: activate with no catalog.json at source-root exits 0 ---
|
|
38
|
+
# This simulates the npx context: source-root points at an npm cache dir with no kits/catalog.json.
|
|
39
|
+
activate_out="$TMP_DIR/activate-npx.out"
|
|
40
|
+
if flow_agents_node "$CLI" activate --dest "$DEST" --source-root "$NPX_SIMULATED_ROOT" >"$activate_out" 2>&1; then
|
|
41
|
+
pass "activate exits 0 when source-root has no catalog.json (npx context)"
|
|
42
|
+
else
|
|
43
|
+
fail "activate exits non-zero when source-root has no catalog.json — false failure in npx context"
|
|
44
|
+
sed -n '1,120p' "$activate_out"
|
|
45
|
+
fi
|
|
46
|
+
|
|
47
|
+
# --- Test 2: missing catalog warning appears but not as an error ---
|
|
48
|
+
if node - "$activate_out" <<'NODE'
|
|
49
|
+
const fs = require("node:fs");
|
|
50
|
+
const raw = fs.readFileSync(process.argv[2], "utf8").trim();
|
|
51
|
+
const data = JSON.parse(raw);
|
|
52
|
+
// errors must be empty — missing catalog is not an error
|
|
53
|
+
if (Array.isArray(data.errors) && data.errors.length > 0) {
|
|
54
|
+
throw new Error(`activate reported errors: ${JSON.stringify(data.errors)}`);
|
|
55
|
+
}
|
|
56
|
+
// warnings may mention catalog (but must not be errors)
|
|
57
|
+
const catalogWarning = Array.isArray(data.warnings) && data.warnings.some((w) => w.includes("catalog") || w.includes("Catalog"));
|
|
58
|
+
// It's fine either way — the key invariant is errors=[].
|
|
59
|
+
console.log(`warnings: ${JSON.stringify(data.warnings)}`);
|
|
60
|
+
console.log(`catalog mentioned in warnings: ${catalogWarning}`);
|
|
61
|
+
console.log("ok");
|
|
62
|
+
NODE
|
|
63
|
+
then
|
|
64
|
+
pass "activate with missing catalog produces no errors (warnings only)"
|
|
65
|
+
else
|
|
66
|
+
fail "activate diagnostic structure incorrect"
|
|
67
|
+
sed -n '1,120p' "$activate_out"
|
|
68
|
+
fi
|
|
69
|
+
|
|
70
|
+
# --- Test 3: user-installed kits ARE activated even without catalog.json ---
|
|
71
|
+
if node - "$activate_out" "$DEST" <<'NODE'
|
|
72
|
+
const fs = require("node:fs");
|
|
73
|
+
const path = require("node:path");
|
|
74
|
+
const raw = fs.readFileSync(process.argv[2], "utf8").trim();
|
|
75
|
+
const data = JSON.parse(raw);
|
|
76
|
+
const dest = process.argv[3];
|
|
77
|
+
// mixed-runtime-kit has a flow with id "mixed.runtime" — it should be in generated_runtime_files
|
|
78
|
+
const ids = new Set(data.generated_runtime_files.map((item) => item.asset_id));
|
|
79
|
+
if (!ids.has("mixed.runtime")) {
|
|
80
|
+
throw new Error(`user-installed kit flow not activated; generated ids: ${[...ids].join(", ")}`);
|
|
81
|
+
}
|
|
82
|
+
// The generated file must exist on disk
|
|
83
|
+
for (const item of data.generated_runtime_files) {
|
|
84
|
+
if (item.asset_class === "activation-manifest") continue;
|
|
85
|
+
const generatedPath = path.join(dest, item.path);
|
|
86
|
+
if (!fs.existsSync(generatedPath)) throw new Error(`generated file missing on disk: ${generatedPath}`);
|
|
87
|
+
}
|
|
88
|
+
console.log("ok");
|
|
89
|
+
NODE
|
|
90
|
+
then
|
|
91
|
+
pass "user-installed kits are activated correctly even without catalog.json"
|
|
92
|
+
else
|
|
93
|
+
fail "user-installed kit flows missing from activation output"
|
|
94
|
+
sed -n '1,120p' "$activate_out"
|
|
95
|
+
fi
|
|
96
|
+
|
|
97
|
+
# --- Test 4: built-in kits also activate when catalog.json IS present (regression guard) ---
|
|
98
|
+
activate_builtin_out="$TMP_DIR/activate-builtin.out"
|
|
99
|
+
if flow_agents_node "$CLI" activate --dest "$DEST" --source-root "$ROOT" >"$activate_builtin_out" 2>&1; then
|
|
100
|
+
pass "activate with catalog.json present still exits 0 (regression guard)"
|
|
101
|
+
else
|
|
102
|
+
fail "activate with catalog.json present failed (regression)"
|
|
103
|
+
sed -n '1,120p' "$activate_builtin_out"
|
|
104
|
+
fi
|
|
105
|
+
|
|
106
|
+
if node - "$activate_builtin_out" <<'NODE'
|
|
107
|
+
const fs = require("node:fs");
|
|
108
|
+
const raw = fs.readFileSync(process.argv[2], "utf8").trim();
|
|
109
|
+
const data = JSON.parse(raw);
|
|
110
|
+
if (Array.isArray(data.errors) && data.errors.length > 0) {
|
|
111
|
+
throw new Error(`regression: activate with catalog.json reported errors: ${JSON.stringify(data.errors)}`);
|
|
112
|
+
}
|
|
113
|
+
// Should have builder kit flows from catalog
|
|
114
|
+
const ids = new Set(data.generated_runtime_files.map((item) => item.asset_id));
|
|
115
|
+
if (!ids.has("builder.shape") && !ids.has("builder.build")) {
|
|
116
|
+
throw new Error(`built-in kit flows missing when catalog.json is present`);
|
|
117
|
+
}
|
|
118
|
+
console.log("ok");
|
|
119
|
+
NODE
|
|
120
|
+
then
|
|
121
|
+
pass "built-in kit flows present when catalog.json is available (regression)"
|
|
122
|
+
else
|
|
123
|
+
fail "built-in kit flows missing — regression introduced"
|
|
124
|
+
sed -n '1,120p' "$activate_builtin_out"
|
|
125
|
+
fi
|
|
126
|
+
|
|
127
|
+
echo ""
|
|
128
|
+
if [[ "$errors" -eq 0 ]]; then
|
|
129
|
+
echo "activate npx-context checks passed."
|
|
130
|
+
exit 0
|
|
131
|
+
fi
|
|
132
|
+
|
|
133
|
+
echo "activate npx-context checks failed: $errors issue(s)."
|
|
134
|
+
exit 1
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# test_flow_kit_install_git.sh — Exercise flow-kit install-git with a file:// git URL fixture.
|
|
3
|
+
# No network required: the fixture is a bare git repo created from the existing valid-local-kit.
|
|
4
|
+
# Implements acceptance criteria for kontourai/flow-agents#56.
|
|
5
|
+
set -uo pipefail
|
|
6
|
+
|
|
7
|
+
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
8
|
+
source "$ROOT/evals/lib/node.sh"
|
|
9
|
+
|
|
10
|
+
errors=0
|
|
11
|
+
TMP_DIR="$(mktemp -d)"
|
|
12
|
+
trap 'rm -rf "$TMP_DIR"' EXIT
|
|
13
|
+
|
|
14
|
+
pass() { echo " ✓ $1"; }
|
|
15
|
+
fail() { echo " ✗ $1"; errors=$((errors + 1)); }
|
|
16
|
+
|
|
17
|
+
CLI="$ROOT/scripts/flow-kit.js"
|
|
18
|
+
VALID_SRC="$ROOT/evals/fixtures/flow-kit-repository/valid-local-kit"
|
|
19
|
+
DEST="$TMP_DIR/install-dest"
|
|
20
|
+
mkdir -p "$DEST"
|
|
21
|
+
|
|
22
|
+
echo "=== install-git Checks (Issue #56) ==="
|
|
23
|
+
|
|
24
|
+
# --- Fixture setup: create a bare git repo from the valid-local-kit fixture ---
|
|
25
|
+
FIXTURE_REPO="$TMP_DIR/fixture-bare-repo"
|
|
26
|
+
FIXTURE_WORKING="$TMP_DIR/fixture-working"
|
|
27
|
+
cp -R "$VALID_SRC" "$FIXTURE_WORKING"
|
|
28
|
+
(cd "$FIXTURE_WORKING" && git init -q && git config user.email "test@test.local" && git config user.name "test" && git add -A && git commit -q -m "init")
|
|
29
|
+
git clone -q --bare "$FIXTURE_WORKING" "$FIXTURE_REPO"
|
|
30
|
+
FILE_URL="file://$FIXTURE_REPO"
|
|
31
|
+
|
|
32
|
+
echo " (fixture repo: $FILE_URL)"
|
|
33
|
+
|
|
34
|
+
# --- Test 1: basic install-git from file:// URL ---
|
|
35
|
+
install_out="$TMP_DIR/install-git.out"
|
|
36
|
+
if flow_agents_node "$CLI" install-git "$FILE_URL" --dest "$DEST" >"$install_out" 2>&1; then
|
|
37
|
+
pass "install-git from file:// URL succeeds"
|
|
38
|
+
else
|
|
39
|
+
fail "install-git from file:// URL failed"
|
|
40
|
+
sed -n '1,80p' "$install_out"
|
|
41
|
+
fi
|
|
42
|
+
|
|
43
|
+
REGISTRY="$DEST/kits/local/installed-kits.json"
|
|
44
|
+
if [[ -f "$REGISTRY" ]]; then
|
|
45
|
+
pass "install-git writes registry file"
|
|
46
|
+
else
|
|
47
|
+
fail "install-git did not write registry file"
|
|
48
|
+
fi
|
|
49
|
+
|
|
50
|
+
if node - "$REGISTRY" "$FILE_URL" <<'NODE'
|
|
51
|
+
const fs = require("node:fs");
|
|
52
|
+
const registry = JSON.parse(fs.readFileSync(process.argv[2], "utf8"));
|
|
53
|
+
const entry = registry.kits[0];
|
|
54
|
+
if (!entry) throw new Error("no kits in registry");
|
|
55
|
+
for (const key of ["id", "source", "hash", "installed_at", "installed_path", "state"]) {
|
|
56
|
+
if (!(key in entry)) throw new Error(`missing metadata key: ${key}`);
|
|
57
|
+
}
|
|
58
|
+
if (entry.id !== "example-kit") throw new Error(`unexpected id: ${entry.id}`);
|
|
59
|
+
if (!entry.hash.startsWith("sha256:")) throw new Error("hash should include sha256 prefix");
|
|
60
|
+
if (Number.isNaN(Date.parse(entry.installed_at))) throw new Error("installed_at should be ISO parseable");
|
|
61
|
+
if (!fs.existsSync(entry.installed_path + "/kit.json")) throw new Error("installed kit copy missing kit.json");
|
|
62
|
+
const expectedSource = process.argv[3];
|
|
63
|
+
if (!entry.source.startsWith(expectedSource.replace(/\/$/, ""))) throw new Error(`source mismatch: ${entry.source}`);
|
|
64
|
+
console.log("ok");
|
|
65
|
+
NODE
|
|
66
|
+
then
|
|
67
|
+
pass "install-git records correct provenance metadata"
|
|
68
|
+
else
|
|
69
|
+
fail "install-git metadata is incomplete"
|
|
70
|
+
fi
|
|
71
|
+
|
|
72
|
+
# --- Test 2: idempotent re-install from same URL ---
|
|
73
|
+
idempotent_out="$TMP_DIR/idempotent.out"
|
|
74
|
+
registry_hash_before="$(shasum -a 256 "$REGISTRY" | awk '{print $1}')"
|
|
75
|
+
if flow_agents_node "$CLI" install-git "$FILE_URL" --dest "$DEST" >"$idempotent_out" 2>&1 \
|
|
76
|
+
&& grep -q "already installed" "$idempotent_out" \
|
|
77
|
+
&& [[ "$registry_hash_before" == "$(shasum -a 256 "$REGISTRY" | awk '{print $1}')" ]]; then
|
|
78
|
+
pass "install-git same-URL reinstall is idempotent"
|
|
79
|
+
else
|
|
80
|
+
fail "install-git same-URL reinstall was not idempotent"
|
|
81
|
+
sed -n '1,80p' "$idempotent_out"
|
|
82
|
+
fi
|
|
83
|
+
|
|
84
|
+
# --- Test 3: #ref fragment syntax ---
|
|
85
|
+
ref_out="$TMP_DIR/ref.out"
|
|
86
|
+
DEST2="$TMP_DIR/dest-with-ref"
|
|
87
|
+
mkdir -p "$DEST2"
|
|
88
|
+
# Re-create fixture repo with a tagged commit so we can test #ref
|
|
89
|
+
FIXTURE_WORKING2="$TMP_DIR/fixture-working2"
|
|
90
|
+
FIXTURE_REPO2="$TMP_DIR/fixture-bare-repo2"
|
|
91
|
+
cp -R "$VALID_SRC" "$FIXTURE_WORKING2"
|
|
92
|
+
(cd "$FIXTURE_WORKING2" && git init -q && git config user.email "test@test.local" && git config user.name "test" && git add -A && git commit -q -m "init" && git tag v1.0)
|
|
93
|
+
git clone -q --bare "$FIXTURE_WORKING2" "$FIXTURE_REPO2"
|
|
94
|
+
FILE_URL2="file://$FIXTURE_REPO2"
|
|
95
|
+
|
|
96
|
+
if flow_agents_node "$CLI" install-git "${FILE_URL2}#v1.0" --dest "$DEST2" >"$ref_out" 2>&1; then
|
|
97
|
+
pass "install-git with #ref fragment succeeds"
|
|
98
|
+
else
|
|
99
|
+
fail "install-git with #ref fragment failed"
|
|
100
|
+
sed -n '1,80p' "$ref_out"
|
|
101
|
+
fi
|
|
102
|
+
|
|
103
|
+
if node - "$DEST2/kits/local/installed-kits.json" "${FILE_URL2}#v1.0" <<'NODE'
|
|
104
|
+
const fs = require("node:fs");
|
|
105
|
+
const registry = JSON.parse(fs.readFileSync(process.argv[2], "utf8"));
|
|
106
|
+
const entry = registry.kits.find((e) => e.id === "example-kit");
|
|
107
|
+
if (!entry) throw new Error("kit not found in registry");
|
|
108
|
+
const expectedSource = process.argv[3];
|
|
109
|
+
if (entry.source !== expectedSource) throw new Error(`source mismatch: expected ${expectedSource}, got ${entry.source}`);
|
|
110
|
+
console.log("ok");
|
|
111
|
+
NODE
|
|
112
|
+
then
|
|
113
|
+
pass "install-git #ref stored in source metadata"
|
|
114
|
+
else
|
|
115
|
+
fail "install-git #ref not stored correctly in source metadata"
|
|
116
|
+
fi
|
|
117
|
+
|
|
118
|
+
# --- Test 4: --ref flag syntax ---
|
|
119
|
+
ref_flag_out="$TMP_DIR/ref-flag.out"
|
|
120
|
+
DEST3="$TMP_DIR/dest-with-ref-flag"
|
|
121
|
+
mkdir -p "$DEST3"
|
|
122
|
+
if flow_agents_node "$CLI" install-git "$FILE_URL2" --ref v1.0 --dest "$DEST3" >"$ref_flag_out" 2>&1; then
|
|
123
|
+
pass "install-git with --ref flag succeeds"
|
|
124
|
+
else
|
|
125
|
+
fail "install-git with --ref flag failed"
|
|
126
|
+
sed -n '1,80p' "$ref_flag_out"
|
|
127
|
+
fi
|
|
128
|
+
|
|
129
|
+
# --- Test 5: missing git URL exits non-zero ---
|
|
130
|
+
missing_url_out="$TMP_DIR/missing-url.out"
|
|
131
|
+
if flow_agents_node "$CLI" install-git --dest "$DEST" >"$missing_url_out" 2>&1; then
|
|
132
|
+
fail "install-git with no URL should exit non-zero"
|
|
133
|
+
sed -n '1,40p' "$missing_url_out"
|
|
134
|
+
else
|
|
135
|
+
pass "install-git with no URL exits non-zero with usage message"
|
|
136
|
+
fi
|
|
137
|
+
|
|
138
|
+
# --- Test 6: invalid git URL exits non-zero ---
|
|
139
|
+
invalid_url_out="$TMP_DIR/invalid-url.out"
|
|
140
|
+
if flow_agents_node "$CLI" install-git "file:///nonexistent-repo-that-does-not-exist" --dest "$DEST" >"$invalid_url_out" 2>&1; then
|
|
141
|
+
fail "install-git with invalid URL should exit non-zero"
|
|
142
|
+
sed -n '1,40p' "$invalid_url_out"
|
|
143
|
+
else
|
|
144
|
+
pass "install-git with invalid URL exits non-zero"
|
|
145
|
+
fi
|
|
146
|
+
|
|
147
|
+
# --- Test 7: catalog.json not mutated ---
|
|
148
|
+
CATALOG_HASH_BEFORE="$(shasum -a 256 "$ROOT/kits/catalog.json" | awk '{print $1}')"
|
|
149
|
+
# (all operations above have already run)
|
|
150
|
+
if [[ "$CATALOG_HASH_BEFORE" == "$(shasum -a 256 "$ROOT/kits/catalog.json" | awk '{print $1}')" ]]; then
|
|
151
|
+
pass "install-git does not mutate source kits/catalog.json"
|
|
152
|
+
else
|
|
153
|
+
fail "source kits/catalog.json changed during install-git test"
|
|
154
|
+
fi
|
|
155
|
+
|
|
156
|
+
echo ""
|
|
157
|
+
if [[ "$errors" -eq 0 ]]; then
|
|
158
|
+
echo "install-git checks passed."
|
|
159
|
+
exit 0
|
|
160
|
+
fi
|
|
161
|
+
|
|
162
|
+
echo "install-git checks failed: $errors issue(s)."
|
|
163
|
+
exit 1
|
|
@@ -43,31 +43,68 @@ const data = JSON.parse(fs.readFileSync(process.argv[2], "utf8"));
|
|
|
43
43
|
const dest = process.argv[3];
|
|
44
44
|
const catalog = process.argv[4];
|
|
45
45
|
if (data.selected_adapter !== "codex-local") throw new Error(`unexpected selected_adapter: ${data.selected_adapter}`);
|
|
46
|
-
|
|
46
|
+
|
|
47
|
+
// supported_asset_classes now includes skills and docs (Issue #58)
|
|
48
|
+
const supported = data.supported_asset_classes;
|
|
49
|
+
for (const expected of ["flows", "skills", "docs"]) {
|
|
50
|
+
if (!supported.includes(expected)) throw new Error(`supported_asset_classes missing ${expected}: ${JSON.stringify(supported)}`);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// generated_runtime_files: flows activated (builder, mixed), skill activated (mixed.skill), activation manifest
|
|
47
54
|
const ids = new Set(data.generated_runtime_files.map((item) => item.asset_id));
|
|
48
55
|
for (const expected of ["builder.shape", "builder.build", "mixed.runtime", "codex-local.activation"]) {
|
|
49
56
|
if (!ids.has(expected)) throw new Error(`missing generated asset: ${expected}`);
|
|
50
57
|
}
|
|
58
|
+
// mixed kit skill should now be in generated_runtime_files, not skipped
|
|
59
|
+
if (!ids.has("mixed.skill")) throw new Error("missing generated asset: mixed.skill (skills should be activated now)");
|
|
60
|
+
// mixed kit doc should now be in generated_runtime_files, not skipped
|
|
61
|
+
if (!ids.has("mixed.docs")) throw new Error("missing generated asset: mixed.docs (docs should be activated now)");
|
|
62
|
+
|
|
63
|
+
// All generated files must exist on disk
|
|
51
64
|
for (const item of data.generated_runtime_files) {
|
|
52
65
|
const generatedPath = path.join(dest, item.path);
|
|
53
66
|
if (!fs.existsSync(generatedPath)) throw new Error(`generated file missing: ${generatedPath}`);
|
|
54
67
|
if (path.resolve(catalog) === path.resolve(generatedPath)) throw new Error("activation generated over kits/catalog.json");
|
|
55
68
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
69
|
+
|
|
70
|
+
// Skills must be written to .flow-agents/runtime/codex/skills/<kit-id>/
|
|
71
|
+
const skillFiles = data.generated_runtime_files.filter((item) => item.asset_class === "skills");
|
|
72
|
+
if (!skillFiles.length) throw new Error("no skills in generated_runtime_files");
|
|
73
|
+
for (const item of skillFiles) {
|
|
74
|
+
if (!item.path.includes(".flow-agents/runtime/codex/skills/")) {
|
|
75
|
+
throw new Error(`skill not under codex skills dir: ${item.path}`);
|
|
76
|
+
}
|
|
77
|
+
if (!fs.existsSync(path.join(dest, item.path))) throw new Error(`skill file missing on disk: ${item.path}`);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Docs must be written to .flow-agents/runtime/codex/docs/<kit-id>/
|
|
81
|
+
const docFiles = data.generated_runtime_files.filter((item) => item.asset_class === "docs");
|
|
82
|
+
if (!docFiles.length) throw new Error("no docs in generated_runtime_files");
|
|
83
|
+
for (const item of docFiles) {
|
|
84
|
+
if (!item.path.includes(".flow-agents/runtime/codex/docs/")) {
|
|
85
|
+
throw new Error(`doc not under codex docs dir: ${item.path}`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// skipped_assets should NOT contain skills or docs any more
|
|
90
|
+
const skippedClasses = new Set(data.skipped_assets.map((item) => item.asset_class));
|
|
91
|
+
if (skippedClasses.has("skills")) throw new Error("skills should not be in skipped_assets after activation fix");
|
|
92
|
+
if (skippedClasses.has("docs")) throw new Error("docs should not be in skipped_assets after activation fix");
|
|
93
|
+
|
|
94
|
+
// adapters, evals, assets still skipped (not activated by codex-local)
|
|
95
|
+
for (const expected of ["adapters", "evals", "assets"]) {
|
|
96
|
+
if (!skippedClasses.has(expected)) throw new Error(`missing skipped asset class: ${expected}`);
|
|
59
97
|
}
|
|
60
98
|
for (const item of data.skipped_assets) {
|
|
61
99
|
for (const key of ["asset_class", "path", "kit_id", "asset_id", "reason"]) {
|
|
62
100
|
if (!(key in item)) throw new Error(`skipped asset missing ${key}: ${JSON.stringify(item)}`);
|
|
63
101
|
}
|
|
64
|
-
if (!item.reason.includes("diagnostic-only")) throw new Error(`unexpected skip reason: ${item.reason}`);
|
|
65
102
|
}
|
|
66
103
|
if (!fs.existsSync(path.join(dest, ".flow-agents/runtime/codex/activation.json"))) throw new Error("runtime activation manifest missing");
|
|
67
104
|
console.log("ok");
|
|
68
105
|
NODE
|
|
69
106
|
then
|
|
70
|
-
pass "diagnostics report default adapter, generated files, and
|
|
107
|
+
pass "diagnostics report default adapter, generated files (flows+skills+docs), and correct skipped_assets (adapters, evals, assets only)"
|
|
71
108
|
else
|
|
72
109
|
fail "activation diagnostics are incomplete"
|
|
73
110
|
sed -n '1,220p' "$OUT"
|
|
@@ -108,6 +145,14 @@ STRANDS_DEST="$TMP_DIR/strands-dest"
|
|
|
108
145
|
STRANDS_OUT="$TMP_DIR/strands-activation.json"
|
|
109
146
|
mkdir -p "$STRANDS_DEST"
|
|
110
147
|
|
|
148
|
+
# Install the mixed kit into strands dest so we can assert skills land there too
|
|
149
|
+
if flow_agents_node "$CLI" install-local "$MIXED_SRC" --dest "$STRANDS_DEST" >"$TMP_DIR/strands-install.out" 2>&1; then
|
|
150
|
+
pass "mixed local kit installs into strands temp destination"
|
|
151
|
+
else
|
|
152
|
+
fail "mixed local kit install failed (strands dest)"
|
|
153
|
+
sed -n '1,160p' "$TMP_DIR/strands-install.out"
|
|
154
|
+
fi
|
|
155
|
+
|
|
111
156
|
# Use the builder kit (stable fixture) — activate for strands-local from the repo source root
|
|
112
157
|
if flow_agents_node "$CLI" activate --dest "$STRANDS_DEST" --source-root "$ROOT" --adapter strands-local --format json >"$STRANDS_OUT" 2>&1; then
|
|
113
158
|
pass "strands-local activation succeeds"
|
|
@@ -125,25 +170,58 @@ const catalog = process.argv[4];
|
|
|
125
170
|
|
|
126
171
|
// Verify selected_adapter
|
|
127
172
|
if (data.selected_adapter !== "strands-local") throw new Error(`expected strands-local, got: ${data.selected_adapter}`);
|
|
128
|
-
|
|
173
|
+
|
|
174
|
+
// supported_asset_classes now includes skills and docs (Issue #58)
|
|
175
|
+
const supported = data.supported_asset_classes;
|
|
176
|
+
for (const expected of ["flows", "skills", "docs"]) {
|
|
177
|
+
if (!supported.includes(expected)) throw new Error(`supported_asset_classes missing ${expected}: ${JSON.stringify(supported)}`);
|
|
178
|
+
}
|
|
129
179
|
|
|
130
180
|
// Verify builder kit flows are generated (builder kit is in catalog.json)
|
|
131
181
|
const ids = new Set(data.generated_runtime_files.map((item) => item.asset_id));
|
|
132
182
|
for (const expected of ["builder.shape", "builder.build", "strands-local.activation"]) {
|
|
133
183
|
if (!ids.has(expected)) throw new Error(`missing generated asset: ${expected}`);
|
|
134
184
|
}
|
|
185
|
+
// mixed kit skill should be in generated_runtime_files
|
|
186
|
+
if (!ids.has("mixed.skill")) throw new Error("missing generated asset: mixed.skill (skills should be activated by strands-local)");
|
|
187
|
+
// mixed kit doc should be in generated_runtime_files
|
|
188
|
+
if (!ids.has("mixed.docs")) throw new Error("missing generated asset: mixed.docs (docs should be activated by strands-local)");
|
|
135
189
|
|
|
136
190
|
// Verify generated runtime files actually exist on disk
|
|
137
191
|
for (const item of data.generated_runtime_files) {
|
|
138
192
|
if (item.asset_class === "activation-manifest") continue;
|
|
139
193
|
const generatedPath = path.join(dest, item.path);
|
|
140
194
|
if (!fs.existsSync(generatedPath)) throw new Error(`generated file missing: ${generatedPath}`);
|
|
141
|
-
// Verify
|
|
142
|
-
if (!item.path.includes(".flow-agents/runtime/strands/flows/")) {
|
|
143
|
-
throw new Error(`generated path not under strands runtime dir: ${item.path}`);
|
|
195
|
+
// Verify flow files are under .flow-agents/runtime/strands/flows/
|
|
196
|
+
if (item.asset_class === "flows" && !item.path.includes(".flow-agents/runtime/strands/flows/")) {
|
|
197
|
+
throw new Error(`generated flow path not under strands runtime dir: ${item.path}`);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Skills must be written to .flow-agents/runtime/strands/skills/<kit-id>/
|
|
202
|
+
const skillFiles = data.generated_runtime_files.filter((item) => item.asset_class === "skills");
|
|
203
|
+
if (!skillFiles.length) throw new Error("no skills in generated_runtime_files for strands-local");
|
|
204
|
+
for (const item of skillFiles) {
|
|
205
|
+
if (!item.path.includes(".flow-agents/runtime/strands/skills/")) {
|
|
206
|
+
throw new Error(`skill not under strands skills dir: ${item.path}`);
|
|
207
|
+
}
|
|
208
|
+
if (!fs.existsSync(path.join(dest, item.path))) throw new Error(`skill file missing on disk: ${item.path}`);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Docs must be written to .flow-agents/runtime/strands/docs/<kit-id>/
|
|
212
|
+
const docFiles = data.generated_runtime_files.filter((item) => item.asset_class === "docs");
|
|
213
|
+
if (!docFiles.length) throw new Error("no docs in generated_runtime_files for strands-local");
|
|
214
|
+
for (const item of docFiles) {
|
|
215
|
+
if (!item.path.includes(".flow-agents/runtime/strands/docs/")) {
|
|
216
|
+
throw new Error(`doc not under strands docs dir: ${item.path}`);
|
|
144
217
|
}
|
|
145
218
|
}
|
|
146
219
|
|
|
220
|
+
// skipped_assets should NOT contain skills or docs
|
|
221
|
+
const skippedClasses = new Set(data.skipped_assets.map((item) => item.asset_class));
|
|
222
|
+
if (skippedClasses.has("skills")) throw new Error("skills should not be in skipped_assets for strands-local");
|
|
223
|
+
if (skippedClasses.has("docs")) throw new Error("docs should not be in skipped_assets for strands-local");
|
|
224
|
+
|
|
147
225
|
// Verify activation.json written at strands runtime dir
|
|
148
226
|
const manifestPath = path.join(dest, ".flow-agents/runtime/strands/activation.json");
|
|
149
227
|
if (!fs.existsSync(manifestPath)) throw new Error("strands runtime activation.json missing");
|
|
@@ -156,14 +234,8 @@ for (const item of manifest.skipped_assets) {
|
|
|
156
234
|
for (const key of ["asset_class", "path", "kit_id", "asset_id", "reason"]) {
|
|
157
235
|
if (!(key in item)) throw new Error(`skipped asset missing ${key}: ${JSON.stringify(item)}`);
|
|
158
236
|
}
|
|
159
|
-
if (!item.reason.includes("diagnostic-only")) throw new Error(`unexpected skip reason: ${item.reason}`);
|
|
160
237
|
}
|
|
161
238
|
|
|
162
|
-
// Non-flow asset classes should appear in skipped_assets
|
|
163
|
-
const skippedClasses = new Set(manifest.skipped_assets.map((item) => item.asset_class));
|
|
164
|
-
// builder kit has flows only; skipped_assets check requires a kit with non-flow assets,
|
|
165
|
-
// which the codex-local path already validates via mixed-runtime-kit above.
|
|
166
|
-
// Here we just confirm the field structure is present.
|
|
167
239
|
if (!Array.isArray(data.skipped_assets)) throw new Error("result skipped_assets is not an array");
|
|
168
240
|
|
|
169
241
|
// Catalog not mutated
|
|
@@ -174,7 +246,7 @@ if (path.resolve(catalog) === path.resolve(path.join(dest, ".flow-agents/runtime
|
|
|
174
246
|
console.log("ok");
|
|
175
247
|
NODE
|
|
176
248
|
then
|
|
177
|
-
pass "strands-local: runtime flow files, activation.json, and skipped_assets present with correct structure"
|
|
249
|
+
pass "strands-local: runtime flow+skill+doc files, activation.json, and skipped_assets present with correct structure"
|
|
178
250
|
else
|
|
179
251
|
fail "strands-local: activation diagnostics incomplete or incorrect"
|
|
180
252
|
sed -n '1,220p' "$STRANDS_OUT"
|
|
@@ -208,6 +280,55 @@ else
|
|
|
208
280
|
sed -n '1,220p' "$TMP_DIR/codex-after-strands.json"
|
|
209
281
|
fi
|
|
210
282
|
|
|
283
|
+
# -------------------------------------------------------------------------
|
|
284
|
+
# Skill activation with a kit that has NO skills (builder kit — flows only)
|
|
285
|
+
# -------------------------------------------------------------------------
|
|
286
|
+
|
|
287
|
+
echo ""
|
|
288
|
+
echo "=== Skills: kit-with-no-skills activates cleanly ==="
|
|
289
|
+
|
|
290
|
+
NO_SKILLS_DEST="$TMP_DIR/no-skills-dest"
|
|
291
|
+
NO_SKILLS_OUT="$TMP_DIR/no-skills-activation.json"
|
|
292
|
+
mkdir -p "$NO_SKILLS_DEST"
|
|
293
|
+
|
|
294
|
+
if flow_agents_node "$CLI" activate --dest "$NO_SKILLS_DEST" --source-root "$ROOT" --format json >"$NO_SKILLS_OUT" 2>&1; then
|
|
295
|
+
pass "activation succeeds for source-root with no skills (builder kit only)"
|
|
296
|
+
else
|
|
297
|
+
fail "activation failed for kit with no skills"
|
|
298
|
+
sed -n '1,220p' "$NO_SKILLS_OUT"
|
|
299
|
+
fi
|
|
300
|
+
|
|
301
|
+
if node - "$NO_SKILLS_OUT" "$NO_SKILLS_DEST" <<'NODE'
|
|
302
|
+
const fs = require("node:fs");
|
|
303
|
+
const path = require("node:path");
|
|
304
|
+
// Use builder-only source root (no installed local kits, built-in kits only)
|
|
305
|
+
const data = JSON.parse(fs.readFileSync(process.argv[2], "utf8"));
|
|
306
|
+
const dest = process.argv[3];
|
|
307
|
+
if (data.selected_adapter !== "codex-local") throw new Error(`expected codex-local, got: ${data.selected_adapter}`);
|
|
308
|
+
// builder kit has no skills or docs — skills dir should not exist (or be empty)
|
|
309
|
+
const skillsDir = path.join(dest, ".flow-agents/runtime/codex/skills");
|
|
310
|
+
// It's fine if the dir doesn't exist; builder kit has no skills
|
|
311
|
+
const docsDir = path.join(dest, ".flow-agents/runtime/codex/docs");
|
|
312
|
+
// builder kit has no docs either
|
|
313
|
+
// No skills or docs in skipped_assets (none declared)
|
|
314
|
+
const skippedClasses = new Set(data.skipped_assets.map((item) => item.asset_class));
|
|
315
|
+
// builder kit only has flows — no skills or docs — so neither should appear in skipped
|
|
316
|
+
if (skippedClasses.has("skills")) throw new Error("builder kit (no skills) should not have skills in skipped_assets");
|
|
317
|
+
if (skippedClasses.has("docs")) throw new Error("builder kit (no docs) should not have docs in skipped_assets");
|
|
318
|
+
// Flows must still be activated
|
|
319
|
+
const ids = new Set(data.generated_runtime_files.map((item) => item.asset_id));
|
|
320
|
+
if (!ids.has("builder.shape")) throw new Error("missing builder.shape flow");
|
|
321
|
+
if (!ids.has("builder.build")) throw new Error("missing builder.build flow");
|
|
322
|
+
if (!fs.existsSync(path.join(dest, ".flow-agents/runtime/codex/activation.json"))) throw new Error("activation.json missing");
|
|
323
|
+
console.log("ok");
|
|
324
|
+
NODE
|
|
325
|
+
then
|
|
326
|
+
pass "kit with no skills activates cleanly — flows activated, no skills or docs in skipped_assets"
|
|
327
|
+
else
|
|
328
|
+
fail "kit with no skills activation check failed"
|
|
329
|
+
sed -n '1,220p' "$NO_SKILLS_OUT"
|
|
330
|
+
fi
|
|
331
|
+
|
|
211
332
|
echo ""
|
|
212
333
|
if [[ "$errors" -eq 0 ]]; then
|
|
213
334
|
echo "Runtime adapter activation checks passed."
|
package/evals/run.sh
CHANGED
|
@@ -135,6 +135,8 @@ run_static() {
|
|
|
135
135
|
echo ""
|
|
136
136
|
bash "$EVAL_DIR/static/test_evidence_refs.sh" || result=1
|
|
137
137
|
echo ""
|
|
138
|
+
bash "$EVAL_DIR/static/test_console_presets.sh" || result=1
|
|
139
|
+
echo ""
|
|
138
140
|
bash "$EVAL_DIR/static/test_repo_hooks.sh" || result=1
|
|
139
141
|
return $result
|
|
140
142
|
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
5
|
+
|
|
6
|
+
pass() {
|
|
7
|
+
echo "PASS: $1"
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
fail() {
|
|
11
|
+
echo "FAIL: $1" >&2
|
|
12
|
+
exit 1
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
assert_hosted_preset() {
|
|
16
|
+
local preset_file="$1"
|
|
17
|
+
local label="$2"
|
|
18
|
+
local default_url override_url
|
|
19
|
+
|
|
20
|
+
default_url="$(
|
|
21
|
+
unset FLOW_AGENTS_KONTOUR_CLOUD_CONSOLE_URL
|
|
22
|
+
# shellcheck source=/dev/null
|
|
23
|
+
source "$preset_file"
|
|
24
|
+
flow_agents_kontour_hosted_console_url
|
|
25
|
+
)"
|
|
26
|
+
[[ "$default_url" == "https://console.kontourai.io" ]] || fail "$label default hosted URL is $default_url"
|
|
27
|
+
pass "$label default hosted URL uses console.kontourai.io"
|
|
28
|
+
|
|
29
|
+
override_url="$(
|
|
30
|
+
export FLOW_AGENTS_KONTOUR_CLOUD_CONSOLE_URL="https://console.override.test"
|
|
31
|
+
# shellcheck source=/dev/null
|
|
32
|
+
source "$preset_file"
|
|
33
|
+
flow_agents_kontour_hosted_console_url
|
|
34
|
+
)"
|
|
35
|
+
[[ "$override_url" == "https://console.override.test" ]] || fail "$label hosted URL override is $override_url"
|
|
36
|
+
pass "$label hosted URL override is preserved"
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
echo "=== Console Preset Contract Checks ==="
|
|
40
|
+
|
|
41
|
+
assert_hosted_preset "$ROOT_DIR/scripts/telemetry/console-presets.sh" "source preset"
|
|
42
|
+
assert_hosted_preset "$ROOT_DIR/context/scripts/telemetry/console-presets.sh" "context preset"
|
|
43
|
+
|
|
44
|
+
if rg -F -q "https://console.kontourai.com" \
|
|
45
|
+
"$ROOT_DIR/scripts/telemetry/console-presets.sh" \
|
|
46
|
+
"$ROOT_DIR/context/scripts/telemetry/console-presets.sh"; then
|
|
47
|
+
fail "preset scripts still reference console.kontourai.com"
|
|
48
|
+
fi
|
|
49
|
+
pass "preset scripts do not reference console.kontourai.com"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kontourai/flow-agents",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Flow Agents — a Kontour product that applies Flow and Veritas discipline as a portable process layer inside the agent tools you already use: Claude Code, Codex, Kiro, opencode, pi, and GitHub Actions — with framework adapters (AWS Strands preview) on the same policy-engine contract.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agents",
|
|
@@ -6,7 +6,7 @@ flow_agents_local_kontour_console_url() {
|
|
|
6
6
|
}
|
|
7
7
|
|
|
8
8
|
flow_agents_kontour_cloud_console_url() {
|
|
9
|
-
printf '%s\n' "${FLOW_AGENTS_KONTOUR_CLOUD_CONSOLE_URL:-https://console.kontourai.
|
|
9
|
+
printf '%s\n' "${FLOW_AGENTS_KONTOUR_CLOUD_CONSOLE_URL:-https://console.kontourai.io}"
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
flow_agents_kontour_hosted_console_url() {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
2
3
|
import * as path from "node:path";
|
|
3
4
|
import { flagBool, flagString, parseArgs } from "../lib/args.js";
|
|
4
5
|
import { buildWorkflowLearningProjection, readWorkflowLearningSources } from "../lib/workflow-learning-projection.js";
|
|
@@ -137,4 +138,9 @@ export function main(argv = process.argv.slice(2)): number {
|
|
|
137
138
|
}
|
|
138
139
|
}
|
|
139
140
|
|
|
140
|
-
|
|
141
|
+
// Use process.exitCode (not process.exit) to allow stdout to be flushed before exit.
|
|
142
|
+
// Resolve real paths to handle symlinks (e.g. /tmp -> /private/tmp on macOS) so the
|
|
143
|
+
// entry-point guard fires correctly when the module is loaded directly as a script.
|
|
144
|
+
const _selfRealPath = (() => { try { return fs.realpathSync(fileURLToPath(import.meta.url)); } catch { return fileURLToPath(import.meta.url); } })();
|
|
145
|
+
const _argv1RealPath = (() => { try { return fs.realpathSync(process.argv[1]); } catch { return process.argv[1]; } })();
|
|
146
|
+
if (_selfRealPath === _argv1RealPath) { process.exitCode = main(); }
|
|
@@ -96,4 +96,9 @@ export function main(argv = process.argv.slice(2)): number {
|
|
|
96
96
|
}
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
-
|
|
99
|
+
// Use process.exitCode (not process.exit) to allow stdout to be flushed before exit.
|
|
100
|
+
// Resolve real paths to handle symlinks (e.g. /tmp -> /private/tmp on macOS) so the
|
|
101
|
+
// entry-point guard fires correctly when the module is loaded directly as a script.
|
|
102
|
+
const _selfRealPath = (() => { try { return fs.realpathSync(fileURLToPath(import.meta.url)); } catch { return fileURLToPath(import.meta.url); } })();
|
|
103
|
+
const _argv1RealPath = (() => { try { return fs.realpathSync(process.argv[1]); } catch { return process.argv[1]; } })();
|
|
104
|
+
if (_selfRealPath === _argv1RealPath) { process.exitCode = main(); }
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import * as fs from "node:fs";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
3
4
|
import * as path from "node:path";
|
|
4
5
|
|
|
5
6
|
type FixtureAuditItem = {
|
|
@@ -151,4 +152,9 @@ export function main(argv = process.argv.slice(2)): number {
|
|
|
151
152
|
}
|
|
152
153
|
}
|
|
153
154
|
|
|
154
|
-
|
|
155
|
+
// Use process.exitCode (not process.exit) to allow stdout to be flushed before exit.
|
|
156
|
+
// Resolve real paths to handle symlinks (e.g. /tmp -> /private/tmp on macOS) so the
|
|
157
|
+
// entry-point guard fires correctly when the module is loaded directly as a script.
|
|
158
|
+
const _selfRealPath = (() => { try { return fs.realpathSync(fileURLToPath(import.meta.url)); } catch { return fileURLToPath(import.meta.url); } })();
|
|
159
|
+
const _argv1RealPath = (() => { try { return fs.realpathSync(process.argv[1]); } catch { return process.argv[1]; } })();
|
|
160
|
+
if (_selfRealPath === _argv1RealPath) { process.exitCode = main(); }
|