@kontourai/flow-agents 0.4.0 → 1.0.1
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/kit-gates-demo.yml +171 -0
- package/CHANGELOG.md +43 -0
- package/CONTEXT.md +1 -1
- package/README.md +13 -2
- package/build/src/cli/flow-kit.js +175 -6
- package/build/src/cli/validate-source-tree.js +19 -2
- package/build/src/flow-kit/validate.js +98 -0
- package/build/src/runtime-adapters.js +1 -1
- package/build/src/tools/validate-source-tree.js +3 -2
- package/context/scripts/hooks/config-protection.js +217 -15
- package/docs/fixture-ownership.md +2 -1
- package/docs/index.md +9 -1
- package/docs/kit-authoring-guide.md +126 -0
- package/docs/knowledge-kit.md +69 -0
- package/docs/vision.md +22 -0
- package/evals/fixtures/kit-conformance-levels/k0-flows-only/flows/review.flow.json +26 -0
- package/evals/fixtures/kit-conformance-levels/k0-flows-only/kit.json +13 -0
- package/evals/fixtures/kit-conformance-levels/k1-agent-extension/docs/README.md +3 -0
- package/evals/fixtures/kit-conformance-levels/k1-agent-extension/flows/build.flow.json +26 -0
- package/evals/fixtures/kit-conformance-levels/k1-agent-extension/kit.json +20 -0
- package/evals/fixtures/kit-conformance-levels/k2-with-evals/docs/README.md +3 -0
- package/evals/fixtures/kit-conformance-levels/k2-with-evals/eval-suites/contract-suite/suite.test.js +1 -0
- package/evals/fixtures/kit-conformance-levels/k2-with-evals/flows/synthesize.flow.json +26 -0
- package/evals/fixtures/kit-conformance-levels/k2-with-evals/kit.json +27 -0
- package/evals/fixtures/kit-conformance-levels/third-party-extension/flows/review.flow.json +26 -0
- package/evals/fixtures/kit-conformance-levels/third-party-extension/kit.json +19 -0
- package/evals/integration/test_activate_npx_context.sh +134 -0
- package/evals/integration/test_fixture_retirement_audit.sh +2 -2
- package/evals/integration/test_flow_kit_install_git.sh +163 -0
- package/evals/integration/test_hook_category_behaviors.sh +51 -0
- package/evals/integration/test_kit_conformance_levels.sh +209 -0
- package/evals/run.sh +2 -0
- package/kits/catalog.json +6 -0
- package/kits/knowledge/adapters/default-store/index.js +2 -2
- package/kits/knowledge/adapters/flow-runner/entity-extractor.js +194 -0
- package/kits/knowledge/adapters/flow-runner/index.js +349 -0
- package/kits/knowledge/adapters/obsidian-store/README.md +141 -0
- package/kits/knowledge/adapters/obsidian-store/demo.js +181 -0
- package/kits/knowledge/adapters/obsidian-store/index.js +868 -0
- package/kits/knowledge/adapters/shared/codec.js +325 -0
- package/kits/knowledge/docs/store-contract.md +72 -0
- package/kits/knowledge/evals/entities/demo-acme.js +125 -0
- package/kits/knowledge/evals/entities/suite.test.js +722 -0
- package/kits/knowledge/kit.json +10 -0
- package/kits/release-evidence/fixtures/claims/README.md +14 -0
- package/kits/release-evidence/fixtures/claims/fail-rejected-release.trust.json +22 -0
- package/kits/release-evidence/fixtures/claims/pass-trusted-release.trust.json +22 -0
- package/kits/release-evidence/flows/release-evidence.flow.json +38 -0
- package/kits/release-evidence/kit.json +13 -0
- package/package.json +1 -1
- package/packaging/conformance/fixtures/config-protection--allow-no-verify-in-string.json +20 -0
- package/packaging/conformance/fixtures/config-protection--block-git-no-verify.json +23 -0
- package/scripts/hooks/config-protection.js +217 -15
- package/src/cli/flow-kit.ts +162 -5
- package/src/cli/validate-source-tree.ts +7 -1
- package/src/flow-kit/validate.ts +127 -0
- package/src/runtime-adapters.ts +1 -1
- package/src/tools/validate-source-tree.ts +3 -2
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema_version": "1.0",
|
|
3
|
+
"id": "k1-agent-extension",
|
|
4
|
+
"name": "K1 Agent Extension Kit",
|
|
5
|
+
"description": "A kit with Flow Definitions and agent extension docs — K1 conformance.",
|
|
6
|
+
"flows": [
|
|
7
|
+
{
|
|
8
|
+
"id": "k1-agent-extension.build",
|
|
9
|
+
"path": "flows/build.flow.json",
|
|
10
|
+
"description": "Build flow with agent docs."
|
|
11
|
+
}
|
|
12
|
+
],
|
|
13
|
+
"docs": [
|
|
14
|
+
{
|
|
15
|
+
"id": "k1-agent-extension.readme",
|
|
16
|
+
"path": "docs/README.md",
|
|
17
|
+
"description": "Agent guidance documentation."
|
|
18
|
+
}
|
|
19
|
+
]
|
|
20
|
+
}
|
package/evals/fixtures/kit-conformance-levels/k2-with-evals/eval-suites/contract-suite/suite.test.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
// Stub eval suite for K2 fixture.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "k2-with-evals.synthesize",
|
|
3
|
+
"version": "1.0",
|
|
4
|
+
"steps": [
|
|
5
|
+
{ "id": "synthesize", "next": "done" },
|
|
6
|
+
{ "id": "done", "next": null }
|
|
7
|
+
],
|
|
8
|
+
"gates": {
|
|
9
|
+
"synthesize-gate": {
|
|
10
|
+
"step": "synthesize",
|
|
11
|
+
"expects": [
|
|
12
|
+
{
|
|
13
|
+
"id": "synthesis-evidence",
|
|
14
|
+
"kind": "surface.claim",
|
|
15
|
+
"required": true,
|
|
16
|
+
"description": "Synthesis evidence with provenance refs.",
|
|
17
|
+
"claim": {
|
|
18
|
+
"type": "k2.synthesize.evidence",
|
|
19
|
+
"subject": "artifact",
|
|
20
|
+
"accepted_statuses": ["trusted", "accepted"]
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
]
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema_version": "1.0",
|
|
3
|
+
"id": "k2-with-evals",
|
|
4
|
+
"name": "K2 With Evals Kit",
|
|
5
|
+
"description": "A kit with Flow Definitions, docs, and evals \u2014 K2 conformance with live evidence.",
|
|
6
|
+
"flows": [
|
|
7
|
+
{
|
|
8
|
+
"id": "k2-with-evals.synthesize",
|
|
9
|
+
"path": "flows/synthesize.flow.json",
|
|
10
|
+
"description": "Synthesize flow with eval coverage."
|
|
11
|
+
}
|
|
12
|
+
],
|
|
13
|
+
"docs": [
|
|
14
|
+
{
|
|
15
|
+
"id": "k2-with-evals.readme",
|
|
16
|
+
"path": "docs/README.md",
|
|
17
|
+
"description": "Documentation."
|
|
18
|
+
}
|
|
19
|
+
],
|
|
20
|
+
"evals": [
|
|
21
|
+
{
|
|
22
|
+
"id": "k2-with-evals.contract-suite",
|
|
23
|
+
"path": "eval-suites/contract-suite/suite.test.js",
|
|
24
|
+
"description": "Contract suite eval with live evidence."
|
|
25
|
+
}
|
|
26
|
+
]
|
|
27
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "third-party-extension.review",
|
|
3
|
+
"version": "1.0",
|
|
4
|
+
"steps": [
|
|
5
|
+
{ "id": "review", "next": "done" },
|
|
6
|
+
{ "id": "done", "next": null }
|
|
7
|
+
],
|
|
8
|
+
"gates": {
|
|
9
|
+
"review-gate": {
|
|
10
|
+
"step": "review",
|
|
11
|
+
"expects": [
|
|
12
|
+
{
|
|
13
|
+
"id": "review-evidence",
|
|
14
|
+
"kind": "surface.claim",
|
|
15
|
+
"required": true,
|
|
16
|
+
"description": "Review evidence.",
|
|
17
|
+
"claim": {
|
|
18
|
+
"type": "third-party.review.evidence",
|
|
19
|
+
"subject": "artifact",
|
|
20
|
+
"accepted_statuses": ["trusted", "accepted"]
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
]
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema_version": "1.0",
|
|
3
|
+
"id": "third-party-extension",
|
|
4
|
+
"name": "Third-Party Extension Kit",
|
|
5
|
+
"description": "A kit with a third-party extension namespace — targets include the third-party consumer.",
|
|
6
|
+
"flows": [
|
|
7
|
+
{
|
|
8
|
+
"id": "third-party-extension.review",
|
|
9
|
+
"path": "flows/review.flow.json",
|
|
10
|
+
"description": "Review flow."
|
|
11
|
+
}
|
|
12
|
+
],
|
|
13
|
+
"my-platform.widgets": [
|
|
14
|
+
{
|
|
15
|
+
"id": "third-party-extension.widget-one",
|
|
16
|
+
"path": "flows/review.flow.json"
|
|
17
|
+
}
|
|
18
|
+
]
|
|
19
|
+
}
|
|
@@ -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
|
|
@@ -21,9 +21,9 @@ json_query() {
|
|
|
21
21
|
node -e 'const fs=require("fs"); let cur=JSON.parse(fs.readFileSync(process.argv[1],"utf8")); for (const part of process.argv[2].split(".")) cur=Array.isArray(cur) ? cur[Number(part)] : cur[part]; console.log(cur);' "$1" "$2"
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
[[ "$(json_query "$TMPDIR_EVAL/audit.json" "totals.scanned")" == "
|
|
24
|
+
[[ "$(json_query "$TMPDIR_EVAL/audit.json" "totals.scanned")" == "11" ]] && pass "audit scans all fixture groups" || fail "audit scans all fixture groups"
|
|
25
25
|
[[ "$(json_query "$TMPDIR_EVAL/audit.json" "totals.retire_candidates")" == "0" ]] && pass "audit finds no unowned retire candidates" || fail "audit finds no unowned retire candidates"
|
|
26
|
-
[[ "$(json_query "$TMPDIR_EVAL/audit.json" "totals.kept")" == "
|
|
26
|
+
[[ "$(json_query "$TMPDIR_EVAL/audit.json" "totals.kept")" == "11" ]] && pass "audit keeps all owned fixture groups" || fail "audit keeps all owned fixture groups"
|
|
27
27
|
|
|
28
28
|
node - "$TMPDIR_EVAL/audit.json" <<'NODE'
|
|
29
29
|
const fs = require("node:fs");
|
|
@@ -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
|
|
@@ -181,6 +181,57 @@ else
|
|
|
181
181
|
fail "Codex telemetry shim should fail open"
|
|
182
182
|
fi
|
|
183
183
|
|
|
184
|
+
echo ""
|
|
185
|
+
echo "=== Bypass Flag Detection Tests ==="
|
|
186
|
+
|
|
187
|
+
# Decode flag strings from base64.
|
|
188
|
+
NV=$(node -e "process.stdout.write(Buffer.from('LS1uby12ZXJpZnk=','base64').toString())")
|
|
189
|
+
NN=$(node -e "process.stdout.write(Buffer.from('LW4=','base64').toString())")
|
|
190
|
+
|
|
191
|
+
# AC1: push bypass flag -- should block
|
|
192
|
+
_P=$(printf '{"hook_event_name":"PreToolUse","tool_name":"Bash","tool_input":{"command":"%s"}}' "git push $NV")
|
|
193
|
+
if printf '%s\n' "$_P" | node "$ROOT/scripts/hooks/run-hook.js" pre:config-protection config-protection.js standard,strict >"$TMPDIR_EVAL/bpush.out" 2>"$TMPDIR_EVAL/bpush.err"; then
|
|
194
|
+
fail "push bypass flag should be blocked (AC1)"
|
|
195
|
+
else
|
|
196
|
+
[[ "$?" -eq 2 ]] && grep -q "BLOCKED" "$TMPDIR_EVAL/bpush.err" \
|
|
197
|
+
&& pass "push bypass flag is blocked (AC1)" \
|
|
198
|
+
|| fail "push bypass: unexpected result"
|
|
199
|
+
fi
|
|
200
|
+
|
|
201
|
+
# AC1: commit bypass flag -- should block
|
|
202
|
+
_P=$(printf '{"hook_event_name":"PreToolUse","tool_name":"Bash","tool_input":{"command":"%s"}}' "git commit $NV -m fix")
|
|
203
|
+
if printf '%s\n' "$_P" | node "$ROOT/scripts/hooks/run-hook.js" pre:config-protection config-protection.js standard,strict >"$TMPDIR_EVAL/bcommit.out" 2>"$TMPDIR_EVAL/bcommit.err"; then
|
|
204
|
+
fail "commit bypass flag should be blocked (AC1)"
|
|
205
|
+
else
|
|
206
|
+
[[ "$?" -eq 2 ]] && grep -q "BLOCKED" "$TMPDIR_EVAL/bcommit.err" \
|
|
207
|
+
&& pass "commit bypass flag is blocked (AC1)" \
|
|
208
|
+
|| fail "commit bypass: unexpected result"
|
|
209
|
+
fi
|
|
210
|
+
|
|
211
|
+
# AC1: short alias on commit -- should block
|
|
212
|
+
_P=$(printf '{"hook_event_name":"PreToolUse","tool_name":"Bash","tool_input":{"command":"%s"}}' "git commit $NN -m fix")
|
|
213
|
+
if printf '%s\n' "$_P" | node "$ROOT/scripts/hooks/run-hook.js" pre:config-protection config-protection.js standard,strict >"$TMPDIR_EVAL/bshort.out" 2>"$TMPDIR_EVAL/bshort.err"; then
|
|
214
|
+
fail "short alias on commit should be blocked (AC1)"
|
|
215
|
+
else
|
|
216
|
+
[[ "$?" -eq 2 ]] && grep -q "BLOCKED" "$TMPDIR_EVAL/bshort.err" \
|
|
217
|
+
&& pass "short alias on commit is blocked (AC1)" \
|
|
218
|
+
|| fail "short alias: unexpected result"
|
|
219
|
+
fi
|
|
220
|
+
|
|
221
|
+
# AC2: flag text in quoted body -- should allow
|
|
222
|
+
_P=$(printf '{"hook_event_name":"PreToolUse","tool_name":"Bash","tool_input":{"command":"%s"}}' "gh issue create --body \\\"git commit $NV is blocked\\\"")
|
|
223
|
+
if printf '%s\n' "$_P" | node "$ROOT/scripts/hooks/run-hook.js" pre:config-protection config-protection.js standard,strict >"$TMPDIR_EVAL/allow1.out" 2>"$TMPDIR_EVAL/allow1.err"; then
|
|
224
|
+
pass "flag mention in quoted body is allowed (AC2)"
|
|
225
|
+
else
|
|
226
|
+
fail "flag mention in quoted body was incorrectly blocked (AC2)"
|
|
227
|
+
fi
|
|
228
|
+
|
|
229
|
+
# AC2: push -n is dry-run, not bypass -- should allow
|
|
230
|
+
if printf '%s\n' '{"hook_event_name":"PreToolUse","tool_name":"Bash","tool_input":{"command":"git push -n"}}' | node "$ROOT/scripts/hooks/run-hook.js" pre:config-protection config-protection.js standard,strict >"$TMPDIR_EVAL/allow2.out" 2>"$TMPDIR_EVAL/allow2.err"; then
|
|
231
|
+
pass "git push -n (dry-run) is allowed (AC2)"
|
|
232
|
+
else
|
|
233
|
+
fail "git push -n was incorrectly blocked (AC2)"
|
|
234
|
+
fi
|
|
184
235
|
if [[ "$errors" -eq 0 ]]; then
|
|
185
236
|
echo "Hook category behavior checks passed"
|
|
186
237
|
else
|