@kontourai/flow-agents 1.3.0 → 2.0.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/CODEOWNERS +29 -0
- package/.github/actions/trust-verify/action.yml +145 -0
- package/.github/workflows/ci.yml +11 -4
- package/.github/workflows/kit-gates-demo.yml +2 -2
- package/.github/workflows/publish-npm.yml +10 -2
- package/.github/workflows/release-please.yml +1 -1
- package/.github/workflows/trust-reconcile.yml +113 -0
- package/AGENTS.md +13 -0
- package/CHANGELOG.md +103 -0
- package/CONTRIBUTING.md +4 -4
- package/README.md +1 -0
- package/agents/tool-planner.json +1 -1
- package/build/src/cli/console-learning-projection.d.ts +1 -0
- package/build/src/cli/effective-backlog-settings.d.ts +1 -0
- package/build/src/cli/fixture-retirement-audit.d.ts +2 -0
- package/build/src/cli/init.d.ts +17 -0
- package/build/src/cli/init.js +242 -20
- package/build/src/cli/kit.d.ts +1 -0
- package/build/src/cli/promote-workflow-artifact.d.ts +1 -0
- package/build/src/cli/publish-change-helper.d.ts +1 -0
- package/build/src/cli/pull-work-provider.d.ts +1 -0
- package/build/src/cli/runtime-adapter.d.ts +1 -0
- package/build/src/cli/telemetry-doctor.d.ts +1 -0
- package/build/src/cli/usage-feedback.d.ts +1 -0
- package/build/src/cli/utterance-check.d.ts +1 -0
- package/build/src/cli/validate-hook-influence.d.ts +1 -0
- package/build/src/cli/validate-source-tree.d.ts +1 -0
- package/build/src/cli/validate-workflow-artifacts.d.ts +2 -0
- package/build/src/cli/validate-workflow-artifacts.js +19 -2
- package/build/src/cli/verify.d.ts +1 -0
- package/build/src/cli/verify.js +90 -0
- package/build/src/cli/veritas-governance.d.ts +1 -0
- package/build/src/cli/workflow-artifact-cleanup-audit.d.ts +1 -0
- package/build/src/cli/workflow-sidecar.d.ts +324 -0
- package/build/src/cli/workflow-sidecar.js +1973 -90
- package/build/src/cli.d.ts +2 -0
- package/build/src/cli.js +2 -3
- package/build/src/flow-kit/validate.d.ts +81 -0
- package/build/src/index.d.ts +5 -0
- package/build/src/index.js +36 -0
- package/build/src/lib/args.d.ts +8 -0
- package/build/src/lib/flow-resolver.d.ts +82 -0
- package/build/src/lib/flow-resolver.js +237 -0
- package/build/src/lib/fs.d.ts +7 -0
- package/build/src/lib/workflow-learning-projection.d.ts +132 -0
- package/build/src/runtime-adapters.d.ts +18 -0
- package/build/src/tools/build-universal-bundles.d.ts +2 -0
- package/build/src/tools/build-universal-bundles.js +34 -22
- package/build/src/tools/common.d.ts +9 -0
- package/build/src/tools/generate-context-map.d.ts +2 -0
- package/build/src/tools/generate-context-map.js +3 -16
- package/build/src/tools/validate-package.d.ts +2 -0
- package/build/src/tools/validate-source-tree.d.ts +2 -0
- package/build/src/tools/validate-source-tree.js +42 -162
- package/context/contracts/artifact-contract.md +10 -0
- package/context/contracts/delivery-contract.md +1 -0
- package/context/contracts/review-contract.md +1 -0
- package/context/contracts/verification-contract.md +2 -0
- package/context/gate-awareness.md +39 -0
- package/context/scripts/hooks/stop-goal-fit.js +632 -70
- package/docs/adr/0001-flow-agents-consumes-flow.md +1 -1
- package/docs/adr/0002-flow-kits-as-extension-unit.md +1 -1
- package/docs/adr/0004-gates-expect-surface-claims.md +2 -0
- package/docs/adr/0005-kubernetes-inspired-resource-contracts.md +2 -0
- package/docs/adr/0007-skill-audit.md +1 -1
- package/docs/adr/0009-canonical-hook-core-kit-boundary.md +95 -0
- package/docs/adr/0010-workflow-trust-state-as-hachure-bundle.md +139 -0
- package/docs/adr/0011-mcp-posture.md +100 -0
- package/docs/adr/0012-agent-coordination-as-liveness-claims.md +119 -0
- package/docs/adr/0013-context-lifecycle.md +151 -0
- package/docs/adr/0014-core-vs-domain-kit-boundary.md +143 -0
- package/docs/adr/0015-flow-flow-agents-boundary-reconciliation.md +120 -0
- package/docs/adr/0016-three-hard-boundary-model.md +71 -0
- package/docs/adr/0017-anti-gaming-trust-security-model.md +155 -0
- package/docs/agent-system-guidebook.md +5 -12
- package/docs/context-map.md +4 -10
- package/docs/developer-architecture.md +14 -0
- package/docs/index.md +3 -2
- package/docs/integrations/framework-adapter.md +19 -6
- package/docs/integrations/index.md +2 -2
- package/docs/north-star.md +4 -4
- package/docs/operating-layers.md +3 -3
- package/docs/plans/adr-0010-phase2-gate-recompute.md +55 -0
- package/docs/repository-structure.md +2 -2
- package/docs/skills-map.md +1 -0
- package/docs/spec/runtime-hook-surface.md +78 -10
- package/docs/standards-register.md +3 -3
- package/docs/survey-utterance-check.md +1 -1
- package/docs/trust-anchor-adoption.md +197 -0
- package/docs/verifiable-trust.md +95 -0
- package/docs/veritas-integration.md +2 -2
- package/docs/workflow-usage-guide.md +69 -0
- package/evals/acceptance/DEMO-false-completion.md +144 -0
- package/evals/acceptance/demo-cast.sh +92 -0
- package/evals/acceptance/demo-false-completion.sh +72 -0
- package/evals/acceptance/demo-real-evidence.sh +104 -0
- package/evals/acceptance/demo.tape +29 -0
- package/evals/acceptance/prove-capture-teeth-declared.sh +335 -0
- package/evals/acceptance/prove-capture-teeth.sh +114 -0
- package/evals/acceptance/prove-teeth.sh +105 -0
- package/evals/ci/antigaming-suite.sh +54 -0
- package/evals/ci/run-baseline.sh +2 -0
- package/evals/fixtures/flow-kit-repository/invalid-missing-extension-asset/flows/review.flow.json +26 -0
- package/evals/fixtures/flow-kit-repository/invalid-missing-extension-asset/kit.json +20 -0
- package/evals/fixtures/flow-kit-repository/valid-unknown-extension/flows/review.flow.json +26 -0
- package/evals/fixtures/flow-kit-repository/valid-unknown-extension/kit.json +18 -0
- package/evals/integration/test_builder_step_producers.sh +379 -0
- package/evals/integration/test_bundle_install.sh +35 -71
- package/evals/integration/test_bundle_lifecycle.sh +39 -2
- package/evals/integration/test_captured_fail_reconciliation.sh +820 -0
- package/evals/integration/test_checkpoint_signing.sh +489 -0
- package/evals/integration/test_claim_lookup.sh +352 -0
- package/evals/integration/test_command_log_integrity.sh +275 -0
- package/evals/integration/test_context_map.sh +0 -2
- package/evals/integration/test_dual_emit_flow_step.sh +278 -0
- package/evals/integration/test_enforcer_expects_driven.sh +281 -0
- package/evals/integration/test_evidence_capture_hook.sh +185 -0
- package/evals/integration/test_flow_kit_repository.sh +2 -0
- package/evals/integration/test_flowdef_session_activation.sh +273 -0
- package/evals/integration/test_flowdef_session_history_preservation.sh +250 -0
- package/evals/integration/test_gate_bypass_chain.sh +448 -0
- package/evals/integration/test_gate_lockdown.sh +1137 -0
- package/evals/integration/test_gate_review_inquiry_records.sh +399 -0
- package/evals/integration/test_goal_fit_escape_hatch.sh +73 -0
- package/evals/integration/test_goal_fit_hook.sh +69 -4
- package/evals/integration/test_goal_fit_rederive.sh +263 -0
- package/evals/integration/test_hook_category_behaviors.sh +14 -0
- package/evals/integration/test_install_merge.sh +1176 -0
- package/evals/integration/test_mint_attestation.sh +373 -0
- package/evals/integration/test_phase_map_and_gate_claim.sh +365 -0
- package/evals/integration/test_publish_delivery.sh +269 -0
- package/evals/integration/test_reconcile_soundness.sh +528 -0
- package/evals/integration/test_resolvefirststep_security.sh +208 -0
- package/evals/integration/test_session_resume_roundtrip.sh +286 -0
- package/evals/integration/test_trust_checkpoint.sh +325 -0
- package/evals/integration/test_trust_reconcile.sh +293 -0
- package/evals/integration/test_verify_cli.sh +208 -0
- package/evals/integration/test_workflow_sidecar_writer.sh +549 -34
- package/evals/lib/node.sh +0 -6
- package/evals/run.sh +47 -0
- package/evals/static/test_library_exports.sh +85 -0
- package/evals/static/test_universal_bundles.sh +15 -0
- package/evals/static/test_workflow_skills.sh +6 -13
- package/install.sh +0 -7
- package/integrations/strands-ts/README.md +25 -15
- package/integrations/veritas/flow-agents.adapter.json +1 -2
- package/kits/builder/flows/build.flow.json +59 -12
- package/kits/builder/kit.json +85 -15
- package/kits/builder/skills/continue-work/SKILL.md +116 -0
- package/kits/builder/skills/deliver/SKILL.md +36 -6
- package/kits/builder/skills/design-probe/SKILL.md +28 -0
- package/kits/builder/skills/execute-plan/SKILL.md +9 -1
- package/kits/builder/skills/gate-review/SKILL.md +234 -0
- package/kits/builder/skills/learning-review/SKILL.md +30 -0
- package/kits/builder/skills/pickup-probe/SKILL.md +29 -0
- package/kits/builder/skills/plan-work/SKILL.md +13 -1
- package/kits/builder/skills/pull-work/SKILL.md +19 -0
- package/kits/knowledge/adapters/default-store/index.js +38 -0
- package/kits/knowledge/adapters/flow-runner/index.js +1620 -0
- package/kits/knowledge/adapters/obsidian-store/index.js +36 -6
- package/kits/knowledge/docs/store-contract.md +314 -0
- package/kits/knowledge/evals/audit-freshness/suite.test.js +368 -0
- package/kits/knowledge/evals/canonicalize-category/suite.test.js +383 -0
- package/kits/knowledge/evals/contract-suite/suite.test.js +111 -0
- package/kits/knowledge/evals/detect-contradictions/suite.test.js +324 -0
- package/kits/knowledge/evals/entities/suite.test.js +40 -0
- package/kits/knowledge/evals/glossary-sync/suite.test.js +416 -0
- package/kits/knowledge/evals/hygiene-review/suite.test.js +396 -0
- package/kits/knowledge/evals/retirement/suite.test.js +145 -0
- package/kits/knowledge/flows/audit-freshness.flow.json +44 -0
- package/kits/knowledge/flows/canonicalize-category.flow.json +44 -0
- package/kits/knowledge/flows/detect-contradictions.flow.json +44 -0
- package/kits/knowledge/flows/glossary-sync.flow.json +61 -0
- package/kits/knowledge/flows/hygiene-review.flow.json +43 -0
- package/kits/knowledge/kit.json +51 -1
- package/package.json +13 -4
- package/packaging/conformance/README.md +10 -2
- package/packaging/conformance/fixtures/evidence-capture--allow-records-command.json +29 -0
- package/packaging/conformance/fixtures/stop-goal-fit--block-bundle-disputed-claim.json +29 -0
- package/packaging/conformance/fixtures/stop-goal-fit--block-capture-contradicts-claimed-pass.json +30 -0
- package/packaging/conformance/fixtures/stop-goal-fit--block-mode.json +23 -0
- package/packaging/conformance/fixtures/stop-goal-fit--off-mode.json +24 -0
- package/packaging/conformance/fixtures/stop-goal-fit--warn-active-delivery.json +5 -2
- package/packaging/conformance/fixtures/stop-goal-fit--warn-no-bundle.json +23 -0
- package/packaging/conformance/fixtures/workflow-steering--reground-active-prompt.json +30 -0
- package/packaging/conformance/fixtures/workflow-steering--reground-session-start.json +30 -0
- package/packaging/conformance/run-conformance.js +1 -1
- package/scripts/README.md +2 -1
- package/scripts/build-universal-bundles.js +0 -1
- package/scripts/ci/mint-attestation.js +221 -0
- package/scripts/ci/trust-reconcile.js +545 -0
- package/scripts/hooks/config-protection.js +423 -1
- package/scripts/hooks/evidence-capture.js +348 -0
- package/scripts/hooks/lib/liveness-read.js +113 -0
- package/scripts/hooks/run-hook.js +6 -1
- package/scripts/hooks/stop-goal-fit.js +1471 -79
- package/scripts/hooks/workflow-steering.js +135 -5
- package/scripts/install-codex-home.sh +39 -0
- package/scripts/install-merge.js +330 -0
- package/src/cli/init.ts +218 -20
- package/src/cli/validate-workflow-artifacts.ts +18 -2
- package/src/cli/verify.ts +100 -0
- package/src/cli/workflow-sidecar.ts +2093 -84
- package/src/cli.ts +2 -3
- package/src/index.ts +53 -0
- package/src/lib/flow-resolver.ts +284 -0
- package/src/tools/build-universal-bundles.ts +34 -21
- package/src/tools/generate-context-map.ts +3 -17
- package/src/tools/validate-source-tree.ts +44 -104
- package/tsconfig.json +1 -0
- package/build/src/tools/filter-installed-packs.js +0 -135
- package/packaging/packs.json +0 -49
- package/scripts/filter-installed-packs.js +0 -2
- package/src/tools/filter-installed-packs.ts +0 -132
|
@@ -5,7 +5,7 @@ import path from "node:path";
|
|
|
5
5
|
import { loadJson, readText, root, walkFiles, writeText } from "./common.js";
|
|
6
6
|
const dist = process.env.FLOW_AGENTS_DIST_DIR ? path.resolve(process.env.FLOW_AGENTS_DIST_DIR) : path.join(root, "dist");
|
|
7
7
|
const manifest = loadJson(path.join(root, "packaging/manifest.json"));
|
|
8
|
-
const
|
|
8
|
+
const pkgVersion = loadJson(path.join(root, "package.json"))["version"] ?? "0.0.0";
|
|
9
9
|
const textExtensions = new Set([".css", ".html", ".js", ".json", ".md", ".sh", ".toml", ".txt", ".yaml", ".yml", ".ts"]);
|
|
10
10
|
const dropDiagnostics = [];
|
|
11
11
|
const printDiagnostics = !["0", "false", "no"].includes(String(process.env.FLOW_AGENTS_EXPORT_DIAGNOSTICS ?? "1").toLowerCase());
|
|
@@ -190,8 +190,9 @@ function generatedAgentsSummary(agents) {
|
|
|
190
190
|
function exportRootAgentsMd(label, agents, taskDir) {
|
|
191
191
|
return `# Universal Agent Bundle (${label})\n\nThis bundle was generated from the canonical source in this repo. Treat the repo root as the source of truth and regenerate the bundle instead of editing exported agent files by hand.\n\n## Shared Conventions\n\n- \`skills/\`, \`context/\`, \`powers/\`, \`prompts/\`, \`scripts/\`, and \`evals/\` were copied from the canonical source.\n- Cross-session task artifacts should live under \`${taskDir}\`.\n- Kiro-only hook wiring was stripped from exported non-Kiro agents to keep the package portable.\n\n## Exported Agents\n\n${generatedAgentsSummary(agents)}\n`;
|
|
192
192
|
}
|
|
193
|
-
|
|
194
|
-
|
|
193
|
+
const CODEX_LIVE_HOOKS_README = `\n## Running with hooks active (live)\n\n\`install.sh\` lays the bundle into a workspace, but a live Codex session needs a \`CODEX_HOME\` that has both the bundle's hooks/scripts AND your real credentials. Use the dedicated installer, which flattens the config to the home root and copies your auth from \`~/.codex\`:\n\n\`\`\`bash\nbash scripts/install-codex-home.sh "$HOME/.flow-agents/codex"\nCODEX_HOME="$HOME/.flow-agents/codex" codex exec --dangerously-bypass-hook-trust -C /path/to/project "<prompt>"\n\`\`\`\n\nThe goal-fit Stop hook then enforces by default (\`FLOW_AGENTS_GOAL_FIT_MODE=block\`); set it to \`warn\` or \`off\` to override.\n`;
|
|
194
|
+
function exportTargetReadme(label, installHint, extra = "") {
|
|
195
|
+
return `# ${label} Bundle\n\nGenerated from the canonical source in this repository.\n\n## Install\n\n\`\`\`bash\n${installHint}\n\`\`\`\n\nThe install ships the full standalone base (skills, agents, powers) plus the\nFlow Kits. Kit depth is activated through the Kit Catalog, not at install time.\n\n## Contents\n\n- Harness-specific agents\n- Shared skills\n- Shared context, powers, prompts, scripts, and evals\n${extra}`;
|
|
195
196
|
}
|
|
196
197
|
function mapClaudeTools(allowedTools) {
|
|
197
198
|
const ordered = [];
|
|
@@ -277,8 +278,8 @@ function shellHook(command, timeout = 10, statusMessage) {
|
|
|
277
278
|
function claudeTelemetry(event) {
|
|
278
279
|
return `bash -lc 'root="\${CLAUDE_PROJECT_DIR:-$(pwd)}"; node "$root/scripts/hooks/claude-telemetry-hook.js" ${event} dev'`;
|
|
279
280
|
}
|
|
280
|
-
function claudePolicy(event, script) {
|
|
281
|
-
return `bash -lc 'root="\${CLAUDE_PROJECT_DIR:-$(pwd)}"; node "$root/scripts/hooks/claude-hook-adapter.js" ${event} ${script.replace(/\.js$/, "")} ${script} default'`;
|
|
281
|
+
function claudePolicy(event, script, envPrefix = "") {
|
|
282
|
+
return `bash -lc 'root="\${CLAUDE_PROJECT_DIR:-$(pwd)}"; ${envPrefix}node "$root/scripts/hooks/claude-hook-adapter.js" ${event} ${script.replace(/\.js$/, "")} ${script} default'`;
|
|
282
283
|
}
|
|
283
284
|
function codexRoot(scriptPath) {
|
|
284
285
|
return `root="\${CODEX_HOME:-}"; if [ -z "$root" ] || [ ! -f "$root/${scriptPath}" ]; then root=$(git rev-parse --show-toplevel 2>/dev/null || pwd); fi`;
|
|
@@ -288,17 +289,23 @@ function codexTelemetry(event) {
|
|
|
288
289
|
return `bash -lc '${codexRoot("scripts/telemetry/telemetry.sh")}; bash "$root/scripts/telemetry/telemetry.sh" permissionRequest dev'`;
|
|
289
290
|
return `bash -lc '${codexRoot("scripts/hooks/codex-telemetry-hook.js")}; node "$root/scripts/hooks/codex-telemetry-hook.js" ${event} dev'`;
|
|
290
291
|
}
|
|
291
|
-
function codexPolicy(script) {
|
|
292
|
-
return `bash -lc '${codexRoot("scripts/hooks/codex-hook-adapter.js")}; node "$root/scripts/hooks/codex-hook-adapter.js" ${script.replace(/\.js$/, "")} ${script} default'`;
|
|
292
|
+
function codexPolicy(script, envPrefix = "") {
|
|
293
|
+
return `bash -lc '${codexRoot("scripts/hooks/codex-hook-adapter.js")}; ${envPrefix}node "$root/scripts/hooks/codex-hook-adapter.js" ${script.replace(/\.js$/, "")} ${script} default'`;
|
|
293
294
|
}
|
|
295
|
+
// Shipped L2 runtimes enforce goal fit by default (mode=block), while remaining
|
|
296
|
+
// operator-overridable via the FLOW_AGENTS_GOAL_FIT_MODE environment variable.
|
|
297
|
+
// The canonical engine default stays warn so the conformance contract is honest.
|
|
298
|
+
const GOAL_FIT_MODE_PREFIX = 'FLOW_AGENTS_GOAL_FIT_MODE="${FLOW_AGENTS_GOAL_FIT_MODE:-block}" ';
|
|
294
299
|
function exportClaudeSettings() {
|
|
295
300
|
const hooks = {};
|
|
296
301
|
for (const event of ["SessionStart", "UserPromptSubmit", "PreToolUse", "PermissionRequest", "PostToolUse", "Stop", "SessionEnd"]) {
|
|
297
302
|
hooks[event] = [{ hooks: [shellHook(claudeTelemetry(event), 10, "Recording Flow Agents telemetry")] }];
|
|
298
303
|
}
|
|
299
|
-
hooks.Stop.push({ hooks: [shellHook(claudePolicy("Stop", "stop-goal-fit.js"), 30, "Running Flow Agents hook policy")] });
|
|
304
|
+
hooks.Stop.push({ hooks: [shellHook(claudePolicy("Stop", "stop-goal-fit.js", GOAL_FIT_MODE_PREFIX), 30, "Running Flow Agents hook policy")] });
|
|
305
|
+
hooks.SessionStart.push({ hooks: [shellHook(claudePolicy("SessionStart", "workflow-steering.js"), 30, "Running Flow Agents hook policy")] });
|
|
300
306
|
hooks.UserPromptSubmit.push({ hooks: [shellHook(claudePolicy("UserPromptSubmit", "workflow-steering.js"), 30, "Running Flow Agents hook policy")] });
|
|
301
307
|
hooks.PostToolUse.push({ hooks: [shellHook(claudePolicy("PostToolUse", "quality-gate.js"), 30, "Running Flow Agents hook policy")] });
|
|
308
|
+
hooks.PostToolUse.push({ hooks: [shellHook(claudePolicy("PostToolUse", "evidence-capture.js"), 30, "Capturing Flow Agents command evidence")] });
|
|
302
309
|
hooks.PreToolUse.push({ hooks: [shellHook(claudePolicy("PreToolUse", "config-protection.js"), 30, "Running Flow Agents hook policy")] });
|
|
303
310
|
return `${JSON.stringify({
|
|
304
311
|
statusLine: { type: "command", command: 'bash -lc \'root="${CLAUDE_PROJECT_DIR:-$(pwd)}"; node "$root/scripts/statusline/flow-agents-statusline.js"\'' },
|
|
@@ -312,8 +319,10 @@ function exportCodexHooks() {
|
|
|
312
319
|
for (const event of ["SessionStart", "UserPromptSubmit", "PreToolUse", "PermissionRequest", "PostToolUse", "Stop"]) {
|
|
313
320
|
hooks[event] = [{ hooks: [shellHook(codexTelemetry(event), 10, "Recording Flow Agents telemetry")] }];
|
|
314
321
|
}
|
|
315
|
-
hooks.Stop.push({ hooks: [shellHook(codexPolicy("stop-goal-fit.js"), 30, "Running Flow Agents hook policy")] });
|
|
322
|
+
hooks.Stop.push({ hooks: [shellHook(codexPolicy("stop-goal-fit.js", GOAL_FIT_MODE_PREFIX), 30, "Running Flow Agents hook policy")] });
|
|
323
|
+
hooks.SessionStart.push({ hooks: [shellHook(codexPolicy("workflow-steering.js"), 30, "Running Flow Agents hook policy")] });
|
|
316
324
|
hooks.UserPromptSubmit.push({ hooks: [shellHook(codexPolicy("workflow-steering.js"), 30, "Running Flow Agents hook policy")] });
|
|
325
|
+
hooks.PostToolUse.push({ hooks: [shellHook(codexPolicy("evidence-capture.js"), 30, "Capturing Flow Agents command evidence")] });
|
|
317
326
|
return `${JSON.stringify({ hooks }, null, 2)}\n`;
|
|
318
327
|
}
|
|
319
328
|
function copySharedContent(targetRoot, targetName, token) {
|
|
@@ -337,21 +346,23 @@ function copySharedContent(targetRoot, targetName, token) {
|
|
|
337
346
|
for (const dir of manifest.optional_copy_dirs ?? [])
|
|
338
347
|
copyTree(path.join(root, dir), path.join(targetRoot, dir), targetName, token);
|
|
339
348
|
writeText(path.join(targetRoot, "build/package.json"), `${JSON.stringify({ type: "module" }, null, 2)}\n`);
|
|
340
|
-
const filterBuilt = path.join(root, "build/src/tools/filter-installed-packs.js");
|
|
341
349
|
const commonBuilt = path.join(root, "build/src/tools/common.js");
|
|
342
|
-
if (fs.existsSync(filterBuilt))
|
|
343
|
-
writeText(path.join(targetRoot, "scripts/filter-installed-packs.mjs"), readText(filterBuilt).replace("./common.js", "./common.mjs"));
|
|
344
350
|
if (fs.existsSync(commonBuilt))
|
|
345
351
|
writeText(path.join(targetRoot, "scripts/common.mjs"), readText(commonBuilt));
|
|
346
352
|
copyTree(path.join(root, "build/src"), path.join(targetRoot, "build/src"), targetName, token);
|
|
347
353
|
}
|
|
348
|
-
function installScript(label, defaultDestDisplay, token, destFallbackShell) {
|
|
354
|
+
function installScript(label, defaultDestDisplay, token, destFallbackShell, mergeConfig, stampConfig) {
|
|
349
355
|
const replaceBlock = token ? `\nexport DEST\nfind "$DEST" -type f \\( -name '*.json' -o -name '*.md' -o -name '*.sh' -o -name '*.js' -o -name '*.ts' -o -name '*.yaml' -o -name '*.yml' \\) -print0 | xargs -0 perl -0pi -e 's#${token.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}#$ENV{DEST}#g'` : "";
|
|
350
356
|
const destFallback = destFallbackShell ? `\nif [[ -z "$DEST" ]]; then\n DEST="${destFallbackShell}"\nfi` : "";
|
|
351
357
|
const destRequired = !destFallbackShell;
|
|
352
358
|
const requiredCheck = destRequired ? `if [[ -z "$DEST" ]]; then\n usage\n exit 2\nfi\n` : "";
|
|
353
359
|
const usageDest = destRequired ? "/path/to/workspace" : defaultDestDisplay;
|
|
354
|
-
|
|
360
|
+
const mergeBlock = mergeConfig
|
|
361
|
+
? `\nif command -v node >/dev/null 2>&1; then\n node "$DEST/scripts/install-merge.js" --config "$DEST/${mergeConfig.configRelPath}" --managed-hooks "$SRC/${mergeConfig.managedConfigRelPath}" --version "${mergeConfig.version}" --install-record "$DEST/.flow-agents/install.json" --runtime "${mergeConfig.runtime}" || true\nfi`
|
|
362
|
+
: stampConfig
|
|
363
|
+
? `\nif command -v node >/dev/null 2>&1; then\n node "$DEST/scripts/install-merge.js" --stamp-only --version "${stampConfig.version}" --install-record "$DEST/.flow-agents/install.json" --runtime "${stampConfig.runtime}" || true\nfi`
|
|
364
|
+
: "";
|
|
365
|
+
return `#!/usr/bin/env bash\nset -euo pipefail\n\nusage() {\n cat >&2 <<'EOF'\nusage: bash install.sh ${usageDest} [options]\n\nOptions:\n --telemetry-sink NAME local-files, local-kontour-console,\n kontour-hosted-console, user-hosted-console,\n or legacy aliases. May be repeated.\n --console-url URL Persist Console telemetry base URL.\n --console-endpoint URL Persist full Console telemetry records endpoint URL.\n --console-token-file PATH\n Read Console telemetry bearer token from a file.\n --console-tenant ID Persist Console tenant identifier.\nEOF\n}\n\nDEST=""\nDEST_SET=0\nCONSOLE_CONFIG_ARGS=()\nwhile [[ $# -gt 0 ]]; do\n case "$1" in\n --telemetry-sink|--telemetry-sinks|--console-url|--console-endpoint|--console-endpoint-url|--console-token-file|--console-tenant|--console-tenant-id)\n [[ $# -ge 2 ]] || { echo "install.sh: $1 requires a value" >&2; exit 2; }\n CONSOLE_CONFIG_ARGS+=("$1" "$2")\n shift 2\n ;;\n --help|-h)\n usage\n exit 0\n ;;\n -*)\n echo "install.sh: unknown option: $1" >&2\n usage\n exit 2\n ;;\n *)\n if [[ "$DEST_SET" -eq 1 ]]; then\n echo "install.sh: unexpected argument: $1" >&2\n usage\n exit 2\n fi\n DEST="$1"\n DEST_SET=1\n shift\n ;;\n esac\ndone${destFallback}\n${requiredCheck}SRC="$(cd "$(dirname "\${BASH_SOURCE[0]}")" && pwd)"\n\nmkdir -p "$DEST"\nrsync -a ${token ? "--delete " : ""}${mergeConfig ? `--exclude="${mergeConfig.configRelPath}" ` : ""}"$SRC"/ "$DEST"/${replaceBlock}${mergeBlock}\nif [[ \${#CONSOLE_CONFIG_ARGS[@]} -gt 0 || -n "\${FLOW_AGENTS_TELEMETRY_SINK:-}" || -n "\${FLOW_AGENTS_TELEMETRY_SINKS:-}" || -n "\${FLOW_AGENTS_CONSOLE_URL:-}" || -n "\${CONSOLE_TELEMETRY_URL:-}" || -n "\${CONSOLE_URL:-}" || -n "\${FLOW_AGENTS_CONSOLE_TOKEN_FILE:-}" || -n "\${CONSOLE_TELEMETRY_TOKEN_FILE:-}" ]]; then\n bash "$DEST/scripts/telemetry/install-console-config.sh" "$DEST/scripts/telemetry/telemetry.conf" "\${CONSOLE_CONFIG_ARGS[@]}"\nfi\necho "Installed ${label} bundle ${token ? "to" : "into"} $DEST"\n`;
|
|
355
366
|
}
|
|
356
367
|
function buildBase(agents) {
|
|
357
368
|
const bundle = path.join(dist, "base");
|
|
@@ -360,7 +371,7 @@ function buildBase(agents) {
|
|
|
360
371
|
writeText(path.join(bundle, ".flow-agents", ".gitkeep"), "");
|
|
361
372
|
writeText(path.join(bundle, "AGENTS.md"), exportRootAgentsMd("Base", agents, ".flow-agents"));
|
|
362
373
|
writeText(path.join(bundle, "README.md"), exportTargetReadme("Base", "bash install.sh /path/to/workspace"));
|
|
363
|
-
writeText(path.join(bundle, "install.sh"), installScript("Base", "/path/to/workspace"));
|
|
374
|
+
writeText(path.join(bundle, "install.sh"), installScript("Base", "/path/to/workspace", undefined, undefined, undefined, { runtime: "base", version: pkgVersion }));
|
|
364
375
|
fs.chmodSync(path.join(bundle, "install.sh"), 0o755);
|
|
365
376
|
}
|
|
366
377
|
function buildKiro(agents) {
|
|
@@ -372,7 +383,7 @@ function buildKiro(agents) {
|
|
|
372
383
|
writeText(path.join(bundle, "agents", `${spec.name}.json`), sanitizeText(`${JSON.stringify(sanitizeAgentJson(spec), null, 2)}\n`, "kiro", token));
|
|
373
384
|
writeText(path.join(bundle, "AGENTS.md"), exportRootAgentsMd("Kiro", agents, ".flow-agents"));
|
|
374
385
|
writeText(path.join(bundle, "README.md"), exportTargetReadme("Kiro", "bash install.sh $HOME/.flow-agents"));
|
|
375
|
-
writeText(path.join(bundle, "install.sh"), installScript("Kiro", "$HOME/.flow-agents", token, '${FLOW_AGENTS_DEST:-$HOME/.flow-agents}'));
|
|
386
|
+
writeText(path.join(bundle, "install.sh"), installScript("Kiro", "$HOME/.flow-agents", token, '${FLOW_AGENTS_DEST:-$HOME/.flow-agents}', undefined, { runtime: "kiro", version: pkgVersion }));
|
|
376
387
|
fs.chmodSync(path.join(bundle, "install.sh"), 0o755);
|
|
377
388
|
}
|
|
378
389
|
function buildClaudeCode(agents) {
|
|
@@ -388,7 +399,7 @@ function buildClaudeCode(agents) {
|
|
|
388
399
|
writeText(path.join(bundle, ".claude/settings.json"), exportClaudeSettings());
|
|
389
400
|
writeText(path.join(bundle, "AGENTS.md"), exportRootAgentsMd("Claude Code", agents, manifest.claude_code.task_dir));
|
|
390
401
|
writeText(path.join(bundle, "README.md"), exportTargetReadme("Claude Code", "bash install.sh /path/to/workspace"));
|
|
391
|
-
writeText(path.join(bundle, "install.sh"), installScript("Claude Code", "/path/to/workspace"));
|
|
402
|
+
writeText(path.join(bundle, "install.sh"), installScript("Claude Code", "/path/to/workspace", undefined, undefined, { configRelPath: ".claude/settings.json", managedConfigRelPath: ".claude/settings.json", runtime: "claude-code", version: pkgVersion }));
|
|
392
403
|
fs.chmodSync(path.join(bundle, "install.sh"), 0o755);
|
|
393
404
|
}
|
|
394
405
|
function buildCodex(agents) {
|
|
@@ -409,8 +420,8 @@ function buildCodex(agents) {
|
|
|
409
420
|
writeText(path.join(bundle, ".codex/skills", name, "SKILL.md"), sanitizeText(readText(src), "codex", "<bundle-root>"));
|
|
410
421
|
}
|
|
411
422
|
writeText(path.join(bundle, "AGENTS.md"), exportRootAgentsMd("Codex", targetAgents, manifest.codex.task_dir));
|
|
412
|
-
writeText(path.join(bundle, "README.md"), exportTargetReadme("Codex", "bash install.sh /path/to/workspace"));
|
|
413
|
-
writeText(path.join(bundle, "install.sh"), installScript("Codex", "/path/to/workspace"));
|
|
423
|
+
writeText(path.join(bundle, "README.md"), exportTargetReadme("Codex", "bash install.sh /path/to/workspace", CODEX_LIVE_HOOKS_README));
|
|
424
|
+
writeText(path.join(bundle, "install.sh"), installScript("Codex", "/path/to/workspace", undefined, undefined, { configRelPath: ".codex/hooks.json", managedConfigRelPath: ".codex/hooks.json", runtime: "codex", version: pkgVersion }));
|
|
414
425
|
fs.chmodSync(path.join(bundle, "install.sh"), 0o755);
|
|
415
426
|
}
|
|
416
427
|
function exportOpencodeAgent(spec) {
|
|
@@ -546,6 +557,7 @@ export const FlowAgentsPlugin = async ({ project, client, $, directory, worktree
|
|
|
546
557
|
const detail = { tool: input && input.tool };
|
|
547
558
|
runTelemetry('tool.execute.after', detail);
|
|
548
559
|
runAdapter('opencode-hook-adapter.js', 'tool.execute.after', detail, 'quality-gate', 'quality-gate.js', 'default');
|
|
560
|
+
runAdapter('opencode-hook-adapter.js', 'tool.execute.after', detail, 'evidence-capture', 'evidence-capture.js', 'default');
|
|
549
561
|
},
|
|
550
562
|
'session.idle': async (_input, _output) => {
|
|
551
563
|
runTelemetry('session.idle');
|
|
@@ -591,7 +603,7 @@ function buildOpencode(agents) {
|
|
|
591
603
|
writeText(path.join(bundle, "opencode.json"), exportOpencodeConfig());
|
|
592
604
|
writeText(path.join(bundle, "AGENTS.md"), exportRootAgentsMd("opencode", agents, manifest.opencode.task_dir));
|
|
593
605
|
writeText(path.join(bundle, "README.md"), exportTargetReadme("opencode", "bash install.sh /path/to/workspace"));
|
|
594
|
-
writeText(path.join(bundle, "install.sh"), installScript("opencode", "/path/to/workspace"));
|
|
606
|
+
writeText(path.join(bundle, "install.sh"), installScript("opencode", "/path/to/workspace", undefined, undefined, { configRelPath: "opencode.json", managedConfigRelPath: "opencode.json", runtime: "opencode", version: pkgVersion }));
|
|
595
607
|
fs.chmodSync(path.join(bundle, "install.sh"), 0o755);
|
|
596
608
|
}
|
|
597
609
|
function exportPiExtension() {
|
|
@@ -679,6 +691,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
679
691
|
pi.on("tool_result", async (_event, _ctx) => {
|
|
680
692
|
runTelemetry("tool_result");
|
|
681
693
|
runAdapter("pi-hook-adapter.js", "tool_result", "quality-gate", "quality-gate.js");
|
|
694
|
+
runAdapter("pi-hook-adapter.js", "tool_result", "evidence-capture", "evidence-capture.js");
|
|
682
695
|
});
|
|
683
696
|
|
|
684
697
|
pi.on("session_shutdown", async (_event, _ctx) => {
|
|
@@ -701,7 +714,7 @@ function buildPi(agents) {
|
|
|
701
714
|
writeText(path.join(bundle, ".pi/extensions/flow-agents.ts"), exportPiExtension());
|
|
702
715
|
writeText(path.join(bundle, "AGENTS.md"), exportRootAgentsMd("pi", agents, manifest.pi.task_dir));
|
|
703
716
|
writeText(path.join(bundle, "README.md"), exportTargetReadme("pi", "bash install.sh /path/to/workspace"));
|
|
704
|
-
writeText(path.join(bundle, "install.sh"), installScript("pi", "/path/to/workspace"));
|
|
717
|
+
writeText(path.join(bundle, "install.sh"), installScript("pi", "/path/to/workspace", undefined, undefined, undefined, { runtime: "pi", version: pkgVersion }));
|
|
705
718
|
fs.chmodSync(path.join(bundle, "install.sh"), 0o755);
|
|
706
719
|
}
|
|
707
720
|
function buildCatalog(agents) {
|
|
@@ -711,7 +724,6 @@ function buildCatalog(agents) {
|
|
|
711
724
|
agents: agents.slice().sort((a, b) => a.name.localeCompare(b.name)).map((spec) => spec.name),
|
|
712
725
|
skills: collectAllSkills().map(({ name }) => name),
|
|
713
726
|
powers: fs.readdirSync(path.join(root, "powers")).filter((name) => fs.existsSync(path.join(root, "powers", name, "mcp.json"))).sort(),
|
|
714
|
-
packs: packs.packs ?? [],
|
|
715
727
|
kits: fs.existsSync(kitsCatalog) ? loadJson(kitsCatalog).kits ?? [] : [],
|
|
716
728
|
};
|
|
717
729
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare const root: string;
|
|
2
|
+
export declare function rel(file: string): string;
|
|
3
|
+
export declare function readText(file: string): string;
|
|
4
|
+
export declare function writeText(file: string, text: string): void;
|
|
5
|
+
export declare function loadJson<T = unknown>(file: string): T;
|
|
6
|
+
export declare function exists(file: string): boolean;
|
|
7
|
+
export declare function walkFiles(dir: string): string[];
|
|
8
|
+
export declare function oneLine(value: string, limit?: number): string;
|
|
9
|
+
export declare function markdownTable(headers: string[], rows: string[][]): string[];
|
|
@@ -10,19 +10,20 @@ const dirDescriptions = {
|
|
|
10
10
|
context: "Shared contracts, routing notes, templates, and reusable guidance.",
|
|
11
11
|
docs: "Long-lived project documentation and GitHub Pages content.",
|
|
12
12
|
evals: "Static, integration, install, and behavioral eval fixtures.",
|
|
13
|
-
powers: "Optional MCP/tool
|
|
13
|
+
powers: "Optional MCP/tool capability bundles.",
|
|
14
14
|
prompts: "Reusable prompt entry points.",
|
|
15
15
|
schemas: "JSON Schema contracts for machine-readable workflow artifacts.",
|
|
16
16
|
scripts: "Build, validation, hook, telemetry, workflow, and import/export utilities.",
|
|
17
17
|
skills: "On-demand capability instructions and workflow primitives.",
|
|
18
18
|
};
|
|
19
|
-
const workflowSkills = new Set(["idea-to-backlog", "pull-work", "plan-work", "execute-plan", "review-work", "verify-work", "evidence-gate", "release-readiness", "learning-review", "deliver", "fix-bug", "tdd-workflow"]);
|
|
19
|
+
const workflowSkills = new Set(["idea-to-backlog", "pull-work", "plan-work", "execute-plan", "review-work", "verify-work", "evidence-gate", "gate-review", "release-readiness", "learning-review", "deliver", "continue-work", "fix-bug", "tdd-workflow"]);
|
|
20
20
|
const commands = [
|
|
21
21
|
["Source tree", "npm run validate:source"],
|
|
22
22
|
["Static suite", "bash evals/run.sh static"],
|
|
23
23
|
["Integration suite", "bash evals/run.sh integration"],
|
|
24
24
|
["Workflow artifacts", "npm run workflow:validate-artifacts -- --require-sidecars --require-critique .flow-agents/<slug>"],
|
|
25
25
|
["Workflow sidecars", "npm run workflow:sidecar -- --help"],
|
|
26
|
+
["Claim lookup", "npm run workflow:sidecar -- claim <id> <dir>"],
|
|
26
27
|
["Context map drift", "npm run context-map:check"],
|
|
27
28
|
["Bundle build", "npm run build:bundles"],
|
|
28
29
|
];
|
|
@@ -154,17 +155,6 @@ function powers() {
|
|
|
154
155
|
const dir = path.join(root, "powers");
|
|
155
156
|
return fs.readdirSync(dir).sort().flatMap((name) => exists(path.join(dir, name, "POWER.md")) ? [[name, rel(path.join(dir, name, "POWER.md"))]] : []);
|
|
156
157
|
}
|
|
157
|
-
function packs() {
|
|
158
|
-
const data = loadJson(path.join(root, "packaging/packs.json"));
|
|
159
|
-
return (data.packs ?? []).map((pack) => [
|
|
160
|
-
String(pack.name ?? ""),
|
|
161
|
-
pack.default ? "yes" : "no",
|
|
162
|
-
String(Array.isArray(pack.skills) ? pack.skills.length : 0),
|
|
163
|
-
String(Array.isArray(pack.agents) ? pack.agents.length : 0),
|
|
164
|
-
String(Array.isArray(pack.powers) ? pack.powers.length : 0),
|
|
165
|
-
oneLine(String(pack.description ?? "")),
|
|
166
|
-
]);
|
|
167
|
-
}
|
|
168
158
|
function latestRuntimeStates(includeRuntime) {
|
|
169
159
|
if (!includeRuntime) {
|
|
170
160
|
return [
|
|
@@ -205,9 +195,6 @@ function render(includeRuntime) {
|
|
|
205
195
|
"## Support Skills", "", ...markdownTable(["Skill", "Source", "When To Load"], supportRows), "",
|
|
206
196
|
"## Agents", "", ...markdownTable(["Agent", "Model", "Tools", "Role"], agents()), "",
|
|
207
197
|
"## Optional Powers", "", ...markdownTable(["Power", "Source"], powers()), "",
|
|
208
|
-
"## Packs", "",
|
|
209
|
-
"Pack composition is defined in `packaging/packs.json`. The current builder exports pack metadata in bundle catalogs, and generated install scripts support opt-in `FLOW_AGENTS_PACKS` filtering while leaving all packs installed by default.", "",
|
|
210
|
-
...markdownTable(["Pack", "Default", "Skills", "Agents", "Powers", "Purpose"], packs()), "",
|
|
211
198
|
"## Current Workflow State", "", ...latestRuntimeStates(includeRuntime), "",
|
|
212
199
|
"## Context Loading Rules", "",
|
|
213
200
|
"- For delivery work, load `deliver`, then the specific primitive skill for the current phase.",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
4
|
import path from "node:path";
|
|
5
|
-
import {
|
|
5
|
+
import { validateKitRepository as validateFlowKitRepository } from "../flow-kit/validate.js";
|
|
6
6
|
import { loadJson, readText, rel, root, walkFiles } from "./common.js";
|
|
7
7
|
class Reporter {
|
|
8
8
|
errors = [];
|
|
@@ -11,14 +11,10 @@ class Reporter {
|
|
|
11
11
|
this.fail(message); }
|
|
12
12
|
}
|
|
13
13
|
const manifestPath = path.join(root, "packaging/manifest.json");
|
|
14
|
-
const packsPath = path.join(root, "packaging/packs.json");
|
|
15
14
|
const kitsCatalogPath = path.join(root, "kits/catalog.json");
|
|
16
15
|
const flowRoot = process.env.FLOW_CLI_ROOT ? path.resolve(process.env.FLOW_CLI_ROOT) : "";
|
|
17
16
|
const flowSchemaPath = flowRoot ? path.join(flowRoot, "schemas", "flow-definition.schema.json") : "";
|
|
18
17
|
const flowCliPath = flowRoot ? ["dist/cli.js", "src/cli.js"].map((candidate) => path.join(flowRoot, candidate)).find((candidate) => fs.existsSync(candidate)) ?? path.join(flowRoot, "dist/cli.js") : "";
|
|
19
|
-
const kitIdRe = /^[a-z][a-z0-9-]*(?:\.[a-z][a-z0-9-]*)*$/;
|
|
20
|
-
const kitAssetSections = new Set(["skills", "docs", "adapters", "evals", "assets"]);
|
|
21
|
-
const kitTopLevelKeys = new Set(["schema_version", "id", "name", "product_name", "description", "flows", ...kitAssetSections]);
|
|
22
18
|
const textRefExtensions = new Set([".md", ".yaml", ".yml", ".json", ".sh", ".js", ".toml"]);
|
|
23
19
|
const ignoredRefDirs = new Set(["node_modules", "__pycache__", ".pytest_cache", ".cache"]);
|
|
24
20
|
const legacyRefRe = /(?<![A-Za-z0-9_.-])(?:agents|agent-cards|context|evals|lib|powers|prompts|scripts|skills)\/[A-Za-z0-9_./@:+-]+/g;
|
|
@@ -32,10 +28,8 @@ const mirroredFiles = new Map([
|
|
|
32
28
|
]);
|
|
33
29
|
const publicScriptWrappers = new Map([
|
|
34
30
|
["scripts/build-universal-bundles.js", { target: "../build/src/tools/build-universal-bundles.js", significantLines: [
|
|
35
|
-
"// Supports FLOW_AGENTS_PACKS through the TypeScript bundle builder.",
|
|
36
31
|
'import("../build/src/tools/build-universal-bundles.js").then(({ main }) => process.exit(main()));',
|
|
37
32
|
] }],
|
|
38
|
-
["scripts/filter-installed-packs.js", { target: "../build/src/tools/filter-installed-packs.js", significantLines: ['import("../build/src/tools/filter-installed-packs.js").then(({ main }) => process.exit(main(process.argv.slice(2))));'] }],
|
|
39
33
|
["scripts/generate-context-map.js", { target: "../build/src/tools/generate-context-map.js", significantLines: ['import("../build/src/tools/generate-context-map.js").then(({ main }) => process.exit(main(process.argv.slice(2))));'] }],
|
|
40
34
|
["scripts/kit.js", { target: "../build/src/cli/kit.js", significantLines: ['import("../build/src/cli/kit.js").then(({ main }) => main().then((code) => process.exit(code)));'] }],
|
|
41
35
|
["scripts/pull-work-provider.js", { target: "../build/src/cli/pull-work-provider.js", significantLines: ['import("../build/src/cli/pull-work-provider.js").then(({ main }) => process.exit(main()));'] }],
|
|
@@ -62,6 +56,7 @@ const hookFilePolicies = new Map([
|
|
|
62
56
|
["scripts/hooks/codex-telemetry-hook.js", { category: "telemetry shim", requiredNeedles: ["codex", "telemetry"] }],
|
|
63
57
|
["scripts/hooks/run-hook.js", { category: "hook runner", requiredNeedles: ["isHookEnabled", "Path traversal rejected"] }],
|
|
64
58
|
["scripts/hooks/config-protection.js", { category: "policy hook", requiredNeedles: ["Config Protection Hook"] }],
|
|
59
|
+
["scripts/hooks/evidence-capture.js", { category: "policy hook", requiredNeedles: ["Evidence Capture Hook"] }],
|
|
65
60
|
["scripts/hooks/governance-audit.sh", { category: "policy hook", requiredNeedles: ["governance-audit.sh", "audit_emit"] }],
|
|
66
61
|
["scripts/hooks/opencode-hook-adapter.js", { category: "runtime adapter", requiredNeedles: ["opencode", "run-hook.js"] }],
|
|
67
62
|
["scripts/hooks/opencode-telemetry-hook.js", { category: "telemetry shim", requiredNeedles: ["opencode", "telemetry"] }],
|
|
@@ -78,6 +73,7 @@ const hookFilePolicies = new Map([
|
|
|
78
73
|
["scripts/hooks/desktop-notify.sh", { category: "local notification helper", requiredNeedles: ["desktop-notify.sh", "osascript"] }],
|
|
79
74
|
["scripts/hooks/lib/audit-transport.sh", { category: "shared hook library", requiredNeedles: ["audit_emit"] }],
|
|
80
75
|
["scripts/hooks/lib/hook-flags.js", { category: "shared hook library", requiredNeedles: ["isHookEnabled"] }],
|
|
76
|
+
["scripts/hooks/lib/liveness-read.js", { category: "shared hook library", requiredNeedles: ["freshHolders", "readLivenessEvents"] }],
|
|
81
77
|
["scripts/hooks/lib/patterns.sh", { category: "shared hook library", requiredNeedles: ["_detect_secrets"] }],
|
|
82
78
|
["scripts/hooks/lib/resolve-formatter.js", { category: "shared hook library", requiredNeedles: ["resolveFormatter"] }],
|
|
83
79
|
]);
|
|
@@ -196,81 +192,7 @@ function validateManifest(reporter, manifest, agentNames) {
|
|
|
196
192
|
for (const agent of manifest.codex?.excluded_agents ?? [])
|
|
197
193
|
reporter.check(agentNames.has(agent), `${rel(manifestPath)}: codex excluded agent '${agent}' does not exist`);
|
|
198
194
|
}
|
|
199
|
-
function
|
|
200
|
-
const data = tryLoadJson(packsPath, reporter);
|
|
201
|
-
if (!data || typeof data !== "object")
|
|
202
|
-
return;
|
|
203
|
-
reporter.check(data.schema_version === "1.0", `${rel(packsPath)}: schema_version must be 1.0`);
|
|
204
|
-
reporter.check(Array.isArray(data.packs) && data.packs.length > 0, `${rel(packsPath)}: packs must be a non-empty list`);
|
|
205
|
-
const skillNames = new Set(fs.readdirSync(path.join(root, "skills")).filter((name) => fs.existsSync(path.join(root, "skills", name, "SKILL.md"))));
|
|
206
|
-
const powerNames = new Set(fs.readdirSync(path.join(root, "powers")).filter((name) => fs.existsSync(path.join(root, "powers", name, "POWER.md"))));
|
|
207
|
-
const names = new Set();
|
|
208
|
-
const defaults = new Set();
|
|
209
|
-
const assigned = { skills: new Set(), agents: new Set(), powers: new Set() };
|
|
210
|
-
(Array.isArray(data.packs) ? data.packs : []).forEach((pack, index) => {
|
|
211
|
-
const name = pack?.name;
|
|
212
|
-
if (typeof name !== "string" || !/^[a-z][a-z0-9-]*$/.test(name)) {
|
|
213
|
-
reporter.fail(`${rel(packsPath)}: packs[${index}].name must be a kebab-case string`);
|
|
214
|
-
return;
|
|
215
|
-
}
|
|
216
|
-
if (names.has(name))
|
|
217
|
-
reporter.fail(`${rel(packsPath)}: duplicate pack name '${name}'`);
|
|
218
|
-
names.add(name);
|
|
219
|
-
if (pack.default === true)
|
|
220
|
-
defaults.add(name);
|
|
221
|
-
reporter.check(typeof pack.description === "string" && !!pack.description, `${rel(packsPath)}: pack '${name}' missing description`);
|
|
222
|
-
for (const [field, available] of [["skills", skillNames], ["agents", agentNames], ["powers", powerNames]]) {
|
|
223
|
-
const values = pack[field] ?? [];
|
|
224
|
-
reporter.check(Array.isArray(values), `${rel(packsPath)}: pack '${name}' .${field} must be a list`);
|
|
225
|
-
const seen = new Set();
|
|
226
|
-
for (const value of Array.isArray(values) ? values : []) {
|
|
227
|
-
if (typeof value !== "string") {
|
|
228
|
-
reporter.fail(`${rel(packsPath)}: pack '${name}' .${field} entry is not a string`);
|
|
229
|
-
continue;
|
|
230
|
-
}
|
|
231
|
-
if (seen.has(value))
|
|
232
|
-
reporter.fail(`${rel(packsPath)}: pack '${name}' has duplicate ${field} entry '${value}'`);
|
|
233
|
-
seen.add(value);
|
|
234
|
-
assigned[field].add(value);
|
|
235
|
-
reporter.check(available.has(value), `${rel(packsPath)}: pack '${name}' references missing ${field.slice(0, -1)} '${value}'`);
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
});
|
|
239
|
-
reporter.check(defaults.has("core"), `${rel(packsPath)}: core pack must be default`);
|
|
240
|
-
const missingSkills = [...skillNames].filter((name) => !assigned.skills.has(name)).sort();
|
|
241
|
-
reporter.check(missingSkills.length === 0, `${rel(packsPath)}: skills missing from all packs: ${missingSkills.join(", ")}`);
|
|
242
|
-
}
|
|
243
|
-
function safeLocalPath(baseDir, pathText, label, reporter) {
|
|
244
|
-
if (typeof pathText !== "string" || !pathText) {
|
|
245
|
-
reporter.fail(`${label} must be a non-empty relative path`);
|
|
246
|
-
return undefined;
|
|
247
|
-
}
|
|
248
|
-
if (path.isAbsolute(pathText)) {
|
|
249
|
-
reporter.fail(`${label} must be relative; absolute paths are not allowed`);
|
|
250
|
-
return undefined;
|
|
251
|
-
}
|
|
252
|
-
if (pathText.split(/[\\/]/).includes("..")) {
|
|
253
|
-
reporter.fail(`${label} must stay inside the kit directory; '..' path traversal is not allowed`);
|
|
254
|
-
return undefined;
|
|
255
|
-
}
|
|
256
|
-
return path.join(baseDir, pathText);
|
|
257
|
-
}
|
|
258
|
-
function validateFlowDefinitionShape(file, data, reporter) {
|
|
259
|
-
const localCli = flowCliPath;
|
|
260
|
-
if (fs.existsSync(localCli)) {
|
|
261
|
-
const result = spawnSync("node", [localCli, "validate-definition", file, "--json"], { encoding: "utf8" });
|
|
262
|
-
if (result.status !== 0)
|
|
263
|
-
reporter.fail(`${rel(file)}: Flow validation failed: ${(result.stderr || result.stdout).trim()}`);
|
|
264
|
-
return;
|
|
265
|
-
}
|
|
266
|
-
if (!data || typeof data !== "object") {
|
|
267
|
-
reporter.fail(`${rel(file)}: Flow Definition must be an object`);
|
|
268
|
-
return;
|
|
269
|
-
}
|
|
270
|
-
for (const key of ["id", "version", "steps", "gates"])
|
|
271
|
-
reporter.check(key in data, `${rel(file)}: missing .${key}`);
|
|
272
|
-
}
|
|
273
|
-
function validateKitRepository(kitDir, reporter) {
|
|
195
|
+
async function validateKitRepository(kitDir, reporter) {
|
|
274
196
|
if (!fs.existsSync(kitDir) || !fs.statSync(kitDir).isDirectory()) {
|
|
275
197
|
reporter.fail(`${rel(kitDir)}: kit directory does not exist`);
|
|
276
198
|
return;
|
|
@@ -279,78 +201,10 @@ function validateKitRepository(kitDir, reporter) {
|
|
|
279
201
|
reporter.check(fs.existsSync(kitJson), `${rel(kitDir)}: missing kit.json at repository root`);
|
|
280
202
|
if (!fs.existsSync(kitJson))
|
|
281
203
|
return;
|
|
282
|
-
const
|
|
283
|
-
|
|
284
|
-
return;
|
|
285
|
-
const unknownKeys = Object.keys(data).filter((key) => !kitTopLevelKeys.has(key)).sort();
|
|
286
|
-
if (unknownKeys.length)
|
|
287
|
-
reporter.fail(`${rel(kitJson)}: unsupported fields ${unknownKeys.join(", ")}; remove them or add them to the Flow Kit Repository contract`);
|
|
288
|
-
reporter.check(data.schema_version === "1.0", `${rel(kitJson)}: .schema_version must be "1.0"`);
|
|
289
|
-
reporter.check(typeof data.id === "string" && kitIdRe.test(data.id), `${rel(kitJson)}: .id must be a stable kebab-case string`);
|
|
290
|
-
reporter.check(typeof data.name === "string" && !!data.name.trim(), `${rel(kitJson)}: .name must be a non-empty string`);
|
|
291
|
-
for (const section of [...kitAssetSections].sort())
|
|
292
|
-
if (section in data) {
|
|
293
|
-
if (!Array.isArray(data[section])) {
|
|
294
|
-
reporter.fail(`${rel(kitJson)}: .${section} must be a list of relative asset paths or objects with path`);
|
|
295
|
-
continue;
|
|
296
|
-
}
|
|
297
|
-
const seenPaths = new Set();
|
|
298
|
-
const seenIds = new Set();
|
|
299
|
-
data[section].forEach((entry, index) => {
|
|
300
|
-
const pathValue = typeof entry === "string" ? entry : entry?.path;
|
|
301
|
-
const assetId = typeof entry === "object" ? entry.id : undefined;
|
|
302
|
-
if (typeof entry === "object") {
|
|
303
|
-
const unknown = Object.keys(entry).filter((key) => !["id", "path", "description"].includes(key)).sort();
|
|
304
|
-
if (unknown.length)
|
|
305
|
-
reporter.fail(`${rel(kitJson)}: ${section}[${index}] has unsupported fields ${unknown.join(", ")}; use id, path, or description`);
|
|
306
|
-
}
|
|
307
|
-
if (assetId !== undefined && (typeof assetId !== "string" || !kitIdRe.test(assetId)))
|
|
308
|
-
reporter.fail(`${rel(kitJson)}: ${section}[${index}].id must be a stable dot/kebab-case string`);
|
|
309
|
-
const assetPath = safeLocalPath(kitDir, pathValue, `${rel(kitJson)}: ${section}[${index}].path`, reporter);
|
|
310
|
-
if (!assetPath)
|
|
311
|
-
return;
|
|
312
|
-
if (seenPaths.has(String(pathValue)))
|
|
313
|
-
reporter.fail(`${rel(kitJson)}: ${section}[${index}].path duplicates '${pathValue}'; declare each asset once`);
|
|
314
|
-
seenPaths.add(String(pathValue));
|
|
315
|
-
if (typeof assetId === "string") {
|
|
316
|
-
if (seenIds.has(assetId))
|
|
317
|
-
reporter.fail(`${rel(kitJson)}: ${section}[${index}].id duplicates '${assetId}'; use a unique asset id`);
|
|
318
|
-
seenIds.add(assetId);
|
|
319
|
-
}
|
|
320
|
-
reporter.check(fs.existsSync(assetPath), `${rel(kitJson)}: ${section}[${index}].path points at missing asset: ${pathValue}; add the file or remove the entry`);
|
|
321
|
-
});
|
|
322
|
-
}
|
|
323
|
-
if (!Array.isArray(data.flows) || !data.flows.length) {
|
|
324
|
-
reporter.fail(`${rel(kitJson)}: .flows must be a non-empty list; add at least one Flow Definition entry`);
|
|
325
|
-
return;
|
|
326
|
-
}
|
|
327
|
-
const seenIds = new Set();
|
|
328
|
-
const seenPaths = new Set();
|
|
329
|
-
data.flows.forEach((flow, index) => {
|
|
330
|
-
if (!flow || typeof flow !== "object") {
|
|
331
|
-
reporter.fail(`${rel(kitJson)}: flows[${index}] must be an object with id and path`);
|
|
332
|
-
return;
|
|
333
|
-
}
|
|
334
|
-
if (typeof flow.id !== "string" || !kitIdRe.test(flow.id))
|
|
335
|
-
reporter.fail(`${rel(kitJson)}: flows[${index}].id must be a stable dot/kebab-case string`);
|
|
336
|
-
else if (seenIds.has(flow.id))
|
|
337
|
-
reporter.fail(`${rel(kitJson)}: flows[${index}].id duplicates '${flow.id}'; use a unique Flow id`);
|
|
338
|
-
else
|
|
339
|
-
seenIds.add(flow.id);
|
|
340
|
-
const flowPath = safeLocalPath(kitDir, flow.path, `${rel(kitJson)}: flows[${index}].path`, reporter);
|
|
341
|
-
if (!flowPath)
|
|
342
|
-
return;
|
|
343
|
-
if (seenPaths.has(String(flow.path))) {
|
|
344
|
-
reporter.fail(`${rel(kitJson)}: flows[${index}].path duplicates '${flow.path}'; declare each Flow Definition once`);
|
|
345
|
-
return;
|
|
346
|
-
}
|
|
347
|
-
seenPaths.add(String(flow.path));
|
|
348
|
-
reporter.check(fs.existsSync(flowPath), `${rel(kitJson)}: flows[${index}].path points at missing Flow Definition: ${flow.path}; add the file or fix the path`);
|
|
349
|
-
if (fs.existsSync(flowPath))
|
|
350
|
-
validateFlowDefinitionShape(flowPath, tryLoadJson(flowPath, reporter), reporter);
|
|
351
|
-
});
|
|
204
|
+
for (const error of await validateFlowKitRepository(kitDir))
|
|
205
|
+
reporter.fail(error);
|
|
352
206
|
}
|
|
353
|
-
function validateKits(reporter) {
|
|
207
|
+
async function validateKits(reporter) {
|
|
354
208
|
reporter.check(fs.existsSync(path.join(root, "kits")), "kits directory missing");
|
|
355
209
|
const catalog = tryLoadJson(kitsCatalogPath, reporter);
|
|
356
210
|
const kits = catalog?.kits;
|
|
@@ -362,17 +216,17 @@ function validateKits(reporter) {
|
|
|
362
216
|
console.log(fs.existsSync(localCli) ? `info: validating kit Flow Definitions with Flow CLI at ${localCli}` : `warning: Flow validator unavailable; source-tree check only verifies Flow Definition top-level shape`);
|
|
363
217
|
else
|
|
364
218
|
console.log("warning: Flow schema not configured; source-tree check only verifies Flow Definition top-level shape. Set FLOW_CLI_ROOT to enable Flow CLI validation. Container validation (kit.json core fields) will delegate to 'flow validate-kit' from @kontourai/flow when FLOW_CLI_ROOT is available.");
|
|
365
|
-
kits.
|
|
219
|
+
for (const [index, entry] of kits.entries()) {
|
|
366
220
|
const kitText = typeof entry === "string" ? entry : ["path", "directory", "dir", "id", "name"].map((key) => entry?.[key]).find((value) => typeof value === "string" && value);
|
|
367
221
|
if (!kitText) {
|
|
368
222
|
reporter.fail(`${rel(kitsCatalogPath)}: kits[${index}] missing path, directory, dir, id, or name`);
|
|
369
|
-
|
|
223
|
+
continue;
|
|
370
224
|
}
|
|
371
225
|
const kitRef = String(kitText).startsWith("kits/") ? path.join(root, kitText) : path.join(root, "kits", kitText);
|
|
372
226
|
const kitDir = path.basename(kitRef) === "kit.json" ? path.dirname(kitRef) : kitRef;
|
|
373
227
|
reporter.check(fs.existsSync(kitDir) && fs.statSync(kitDir).isDirectory(), `${rel(kitsCatalogPath)}: kits[${index}] points at missing kit folder: ${kitText}`);
|
|
374
|
-
validateKitRepository(kitDir, reporter);
|
|
375
|
-
}
|
|
228
|
+
await validateKitRepository(kitDir, reporter);
|
|
229
|
+
}
|
|
376
230
|
}
|
|
377
231
|
function validateAgentPaths(reporter, manifest) {
|
|
378
232
|
for (const file of walkFiles(path.join(root, "agents")).filter((item) => item.endsWith(".json"))) {
|
|
@@ -480,6 +334,32 @@ function validatePublicScriptWrappers(reporter) {
|
|
|
480
334
|
reporter.check(JSON.stringify(significantLines) === JSON.stringify(policy.significantLines), `${file}: public wrapper must match the exact thin launcher body for ${policy.target}`);
|
|
481
335
|
}
|
|
482
336
|
}
|
|
337
|
+
function validateAdrNumbers(reporter) {
|
|
338
|
+
// Each ADR (a docs/adr file with an `# ADR NNNN:` heading) must own a unique
|
|
339
|
+
// number, and its filename prefix must match that number. Companion/index docs
|
|
340
|
+
// without an ADR heading (e.g. a numbered skill-audit tied to an ADR) are
|
|
341
|
+
// intentionally skipped. Guards against concurrent number collisions like the
|
|
342
|
+
// duplicate ADR 0014 from PRs #180/#172.
|
|
343
|
+
const adrDir = path.join(root, "docs/adr");
|
|
344
|
+
if (!fs.existsSync(adrDir))
|
|
345
|
+
return;
|
|
346
|
+
const byNumber = new Map();
|
|
347
|
+
for (const file of walkFiles(adrDir)) {
|
|
348
|
+
if (path.extname(file) !== ".md")
|
|
349
|
+
continue;
|
|
350
|
+
const heading = readText(file).match(/^#\s+ADR\s+(\d{4}):/m);
|
|
351
|
+
if (!heading)
|
|
352
|
+
continue; // not an ADR decision doc
|
|
353
|
+
const num = heading[1];
|
|
354
|
+
reporter.check(path.basename(file).startsWith(`${num}-`), `${rel(file)}: ADR heading number ${num} does not match the filename prefix`);
|
|
355
|
+
const list = byNumber.get(num) ?? [];
|
|
356
|
+
list.push(rel(file));
|
|
357
|
+
byNumber.set(num, list);
|
|
358
|
+
}
|
|
359
|
+
for (const [num, files] of byNumber) {
|
|
360
|
+
reporter.check(files.length === 1, `docs/adr: duplicate ADR number ${num} — ${files.join(", ")}. ADR numbers must be unique; renumber one.`);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
483
363
|
function validateHookInventory(reporter) {
|
|
484
364
|
const readme = readText(path.join(root, "scripts/README.md"));
|
|
485
365
|
const hookFiles = walkFiles(path.join(root, "scripts/hooks"))
|
|
@@ -605,7 +485,7 @@ function validateNoFirstPartyPythonCommands(reporter) {
|
|
|
605
485
|
reporter.fail(`${relative}: direct first-party Python command reference is not allowed; use npm/flow-agents TypeScript commands`);
|
|
606
486
|
}
|
|
607
487
|
}
|
|
608
|
-
export function main(argv = process.argv.slice(2)) {
|
|
488
|
+
export async function main(argv = process.argv.slice(2)) {
|
|
609
489
|
const kitIndex = argv.indexOf("--kit");
|
|
610
490
|
if (kitIndex >= 0) {
|
|
611
491
|
const kitDir = argv[kitIndex + 1];
|
|
@@ -619,7 +499,7 @@ export function main(argv = process.argv.slice(2)) {
|
|
|
619
499
|
console.log(`info: validating kit Flow Definitions with Flow CLI at ${localCli}`);
|
|
620
500
|
else
|
|
621
501
|
console.log("warning: Flow validation surface unavailable; local kit check uses the minimal Flow Definition fallback");
|
|
622
|
-
validateKitRepository(path.resolve(kitDir), reporter);
|
|
502
|
+
await validateKitRepository(path.resolve(kitDir), reporter);
|
|
623
503
|
if (reporter.errors.length) {
|
|
624
504
|
console.log("Flow Kit repository validation failed:");
|
|
625
505
|
for (const error of reporter.errors)
|
|
@@ -635,14 +515,14 @@ export function main(argv = process.argv.slice(2)) {
|
|
|
635
515
|
validateAgentCards(reporter, agentNames);
|
|
636
516
|
validatePowers(reporter);
|
|
637
517
|
validateManifest(reporter, manifest, agentNames);
|
|
638
|
-
|
|
639
|
-
validateKits(reporter);
|
|
518
|
+
await validateKits(reporter);
|
|
640
519
|
validateAgentPaths(reporter, manifest);
|
|
641
520
|
validateLegacyRefs(reporter);
|
|
642
521
|
validateMirrors(reporter);
|
|
643
522
|
validateUsageFeedbackFiles(reporter);
|
|
644
523
|
validatePublicScriptWrappers(reporter);
|
|
645
524
|
validateHookInventory(reporter);
|
|
525
|
+
validateAdrNumbers(reporter);
|
|
646
526
|
validateFixtureOwnership(reporter);
|
|
647
527
|
validatePackageCommandSurface(reporter);
|
|
648
528
|
validateNoFirstPartyPythonFiles(reporter);
|
|
@@ -672,5 +552,5 @@ catch {
|
|
|
672
552
|
return process.argv[1];
|
|
673
553
|
} })();
|
|
674
554
|
if (_selfRealPath === _argv1RealPath) {
|
|
675
|
-
process.exitCode =
|
|
555
|
+
main().then((code) => { process.exitCode = code; });
|
|
676
556
|
}
|
|
@@ -20,6 +20,16 @@ The artifact root is local working memory unless a workflow explicitly promotes
|
|
|
20
20
|
- Do not commit local workflow runtime roots such as `.flow-agents/<slug>/` as durable policy unless a repository-specific contract explicitly says that artifact is promoted.
|
|
21
21
|
- Do not commit local workflow runtime roots such as `.flow-agents/<slug>/`; final acceptance must promote durable content before merge.
|
|
22
22
|
|
|
23
|
+
## Persistence Integrity
|
|
24
|
+
|
|
25
|
+
Writing a durable artifact must **fail loud, never fail-open.** If a record (state, evidence, a
|
|
26
|
+
trust.bundle, a claim) cannot be persisted — a missing dependency, a validation failure, an I/O
|
|
27
|
+
error — the operation **fails with the reason**; it must not return success while silently
|
|
28
|
+
dropping the write. A silently-skipped persist is **data loss**, not a degraded mode, and is
|
|
29
|
+
invisible to the caller that depended on it. Callers act on persistence **return values**, not
|
|
30
|
+
just thrown exceptions. (See #160: an ignored `{written:false}` from the bundle writer dropped
|
|
31
|
+
records under concurrency.)
|
|
32
|
+
|
|
23
33
|
## Required Artifact Types
|
|
24
34
|
|
|
25
35
|
### Structured Sidecars
|
|
@@ -61,6 +61,7 @@ After CI passes and the work is merged, released, or otherwise accepted:
|
|
|
61
61
|
- [ ] durable docs link back to the provider record, archived plan, or session artifact when useful
|
|
62
62
|
- [ ] local `.flow-agents/` runtime artifacts remain untracked, and durable outcomes are promoted before merge to `main`
|
|
63
63
|
- [ ] follow-up issues or learning-review items created for deferred work
|
|
64
|
+
- [ ] **workspace cleaned up after a confirmed merge**: the merge is verified from the provider's own merge record (a merge commit / `mergedAt`), not a green check or a command exit code; then the isolated worktree is removed and the now-merged branch is deleted locally and on the remote, honoring the `worktree_lifecycle` (`retain_until: pr_merged`) recorded at selection. Never delete a branch or worktree before the merge is confirmed. A delivery is not complete while it leaves a stale worktree or merged branch behind.
|
|
64
65
|
|
|
65
66
|
## Distribution Rule
|
|
66
67
|
|
|
@@ -59,6 +59,7 @@ All reviewers are read-only reporters. They may inspect files, run read-only ana
|
|
|
59
59
|
Attempt relevant perspectives and record findings:
|
|
60
60
|
|
|
61
61
|
- Code quality: readability, naming, function/file size, error handling, duplication, maintainability
|
|
62
|
+
- Failure handling: callers act on failure *return values*, not just exceptions — flag fail-open on any data-persisting path (per the persistence-integrity invariant in the artifact contract)
|
|
62
63
|
- Correctness risks: edge cases, unintended behavior, unsafe assumptions, missing tests
|
|
63
64
|
- Standards fit: project conventions, local architecture, public contracts, documented decisions
|
|
64
65
|
- Security: secrets, injection, XSS, path traversal, auth/authz, unsafe external calls, vulnerable dependencies
|