@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
package/src/cli.ts
CHANGED
|
@@ -14,19 +14,18 @@ import { main as veritasGovernance } from "./cli/veritas-governance.js";
|
|
|
14
14
|
import { main as workflowArtifactCleanupAudit } from "./cli/workflow-artifact-cleanup-audit.js";
|
|
15
15
|
import { main as buildBundles } from "./tools/build-universal-bundles.js";
|
|
16
16
|
import { main as contextMap } from "./tools/generate-context-map.js";
|
|
17
|
-
import { main as filterInstalledPacks } from "./tools/filter-installed-packs.js";
|
|
18
17
|
import { main as validateSource } from "./tools/validate-source-tree.js";
|
|
19
18
|
import { main as validatePackage } from "./tools/validate-package.js";
|
|
20
19
|
import { main as validateHookInfluence } from "./cli/validate-hook-influence.js";
|
|
21
20
|
import { main as runtimeAdapter } from "./cli/runtime-adapter.js";
|
|
22
21
|
import { main as utteranceCheck } from "./cli/utterance-check.js";
|
|
22
|
+
import { main as verify } from "./cli/verify.js";
|
|
23
23
|
|
|
24
24
|
const availableCommands = new Map<string, (argv: string[]) => number | Promise<number>>([
|
|
25
25
|
["build-bundles", () => buildBundles()],
|
|
26
26
|
["console-learning-projection", consoleLearningProjection],
|
|
27
27
|
["context-map", contextMap],
|
|
28
28
|
["effective-backlog-settings", effectiveBacklogSettings],
|
|
29
|
-
["filter-installed-packs", filterInstalledPacks],
|
|
30
29
|
["fixture-retirement-audit", fixtureRetirementAudit],
|
|
31
30
|
["kit", kit],
|
|
32
31
|
["init", init],
|
|
@@ -40,6 +39,7 @@ const availableCommands = new Map<string, (argv: string[]) => number | Promise<n
|
|
|
40
39
|
["veritas-governance", veritasGovernance],
|
|
41
40
|
["validate-package", validatePackage],
|
|
42
41
|
["validate-hook-influence", validateHookInfluence],
|
|
42
|
+
["verify", verify],
|
|
43
43
|
["validate-source", validateSource],
|
|
44
44
|
["workflow-artifact-cleanup-audit", workflowArtifactCleanupAudit],
|
|
45
45
|
]);
|
|
@@ -49,7 +49,6 @@ const aliases = new Map<string, string>([
|
|
|
49
49
|
["flow-agents-console-learning-projection", "console-learning-projection"],
|
|
50
50
|
["flow-agents-context-map", "context-map"],
|
|
51
51
|
["flow-agents-effective-backlog-settings", "effective-backlog-settings"],
|
|
52
|
-
["flow-agents-filter-installed-packs", "filter-installed-packs"],
|
|
53
52
|
["flow-agents-fixture-retirement-audit", "fixture-retirement-audit"],
|
|
54
53
|
["flow-agents-kit", "kit"],
|
|
55
54
|
["flow-agents-promote-workflow-artifact", "promote-workflow-artifact"],
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public library surface for `@kontourai/flow-agents`.
|
|
3
|
+
*
|
|
4
|
+
* Native orchestration hosts can import the canonical workflow-sidecar
|
|
5
|
+
* writer/validator here instead of shelling out to the
|
|
6
|
+
* `flow-agents-workflow-sidecar` CLI or reimplementing validated
|
|
7
|
+
* read / merge / write of workflow evidence. This is the same code the CLI
|
|
8
|
+
* runs — importing it does not execute the CLI.
|
|
9
|
+
*
|
|
10
|
+
* The sidecar JSON Schemas ship under `schemas/` and can be validated against
|
|
11
|
+
* directly; the helpers below are the canonical writer/validator that produce
|
|
12
|
+
* and check conforming artifacts.
|
|
13
|
+
*
|
|
14
|
+
* @module
|
|
15
|
+
*/
|
|
16
|
+
import * as path from "node:path";
|
|
17
|
+
import { loadJson as _loadJson, writeJson as _writeJson } from "./cli/workflow-sidecar.js";
|
|
18
|
+
|
|
19
|
+
export {
|
|
20
|
+
// Trust-bundle (Hachure) validation — the same validator the writer uses.
|
|
21
|
+
validateTrustBundle,
|
|
22
|
+
// Evidence / check / learning validation + normalization. These throw on
|
|
23
|
+
// invalid input (with the same messages the CLI surfaces) and return the
|
|
24
|
+
// normalized object on success.
|
|
25
|
+
normalizeCheck,
|
|
26
|
+
normalizeFinding,
|
|
27
|
+
normalizeLearning,
|
|
28
|
+
normalizeEvidenceRefs,
|
|
29
|
+
validateEvidenceRef,
|
|
30
|
+
validateLearningCorrection,
|
|
31
|
+
// Sidecar read / merge / write primitives.
|
|
32
|
+
loadJson,
|
|
33
|
+
writeJson,
|
|
34
|
+
appendJsonl,
|
|
35
|
+
sidecarBase,
|
|
36
|
+
writeState,
|
|
37
|
+
// Schema vocabularies (the allowed status/phase/kind values).
|
|
38
|
+
statuses,
|
|
39
|
+
phases,
|
|
40
|
+
checkKinds,
|
|
41
|
+
checkStatuses,
|
|
42
|
+
verdicts,
|
|
43
|
+
} from "./cli/workflow-sidecar.js";
|
|
44
|
+
|
|
45
|
+
/** Read a sidecar JSON file from a workflow artifact directory; returns `{}` if absent. */
|
|
46
|
+
export function readSidecar(dir: string, name: string): Record<string, any> {
|
|
47
|
+
return _loadJson(path.join(dir, name));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Write a sidecar JSON file into a workflow artifact directory (pretty-printed, trailing newline). */
|
|
51
|
+
export function writeSidecar(dir: string, name: string, payload: Record<string, any>): void {
|
|
52
|
+
_writeJson(path.join(dir, name), payload);
|
|
53
|
+
}
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Flow-Definition resolver — ADR 0016 Abstraction A (Option B), Phase P-a.
|
|
3
|
+
*
|
|
4
|
+
* Shared resolver consumed by both the producer (workflow-sidecar.ts) and,
|
|
5
|
+
* later, the enforcer (stop-goal-fit.js via P-c). This is the SINGLE source
|
|
6
|
+
* of truth for (active_flow_id, active_step_id) → FlowDefinition gate
|
|
7
|
+
* expects[] resolution. Neither consumer duplicates this logic.
|
|
8
|
+
*
|
|
9
|
+
* Design:
|
|
10
|
+
* - Pure and synchronous — no async, no throws.
|
|
11
|
+
* - Returns null on ENOENT, parse error, or missing gate (fail-open).
|
|
12
|
+
* - Kit-agnostic: kitId = flowId.split(".")[0]; no hardcoded kit list.
|
|
13
|
+
* - Honors FLOW_AGENTS_FLOW_DEFS_DIR env-var override for custom installs.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import * as fs from "node:fs";
|
|
17
|
+
import * as path from "node:path";
|
|
18
|
+
|
|
19
|
+
// ─── Security: Layer 1 traversal defense ─────────────────────────────────────
|
|
20
|
+
//
|
|
21
|
+
// Both kitId and flowName originate from agent-writable sources (active_flow_id
|
|
22
|
+
// in current.json, and FLOW_AGENTS_FLOW_DEFS_DIR set by the runtime). A crafted
|
|
23
|
+
// value like "builder.../../../.flow-agents/slug/fake-flow" produces:
|
|
24
|
+
// kitId = "builder"
|
|
25
|
+
// flowName = "../../../.flow-agents/slug/fake-flow"
|
|
26
|
+
// which resolves OUTSIDE kits/ via path.join traversal.
|
|
27
|
+
//
|
|
28
|
+
// SLUG_RE closes this: it rejects any value containing path separators, dots,
|
|
29
|
+
// or characters outside the safe identifier alphabet. Only [a-zA-Z0-9_-] is
|
|
30
|
+
// allowed, making traversal sequences impossible.
|
|
31
|
+
//
|
|
32
|
+
// Belt-and-suspenders: after building the path we also confirm the resolved
|
|
33
|
+
// absolute path stays inside the expected root directory.
|
|
34
|
+
|
|
35
|
+
/** Strict slug pattern — allows only URL-safe identifier chars. */
|
|
36
|
+
const SLUG_RE = /^[a-zA-Z0-9_-]+$/;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Returns true when the given resolved absolute path falls within a .flow-agents
|
|
40
|
+
* directory (an agent-writable area). Used to reject FLOW_AGENTS_FLOW_DEFS_DIR
|
|
41
|
+
* overrides that point into agent-controlled storage.
|
|
42
|
+
*/
|
|
43
|
+
function isAgentWritableDir(resolvedDir: string): boolean {
|
|
44
|
+
return resolvedDir.split(path.sep).includes(".flow-agents");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Build and validate the FlowDefinition file path.
|
|
49
|
+
*
|
|
50
|
+
* Returns the validated absolute file path, or null when:
|
|
51
|
+
* - kitId or flowName contains chars outside SLUG_RE (rejects traversal)
|
|
52
|
+
* - FLOW_AGENTS_FLOW_DEFS_DIR resolves into a .flow-agents directory
|
|
53
|
+
* - The resolved path escapes the expected root (belt-and-suspenders)
|
|
54
|
+
*
|
|
55
|
+
* When the override is unsafe (points into .flow-agents), falls back silently
|
|
56
|
+
* to the repoRoot/kits/ path so the resolver still works for legit flows.
|
|
57
|
+
*/
|
|
58
|
+
export function resolveFlowFilePath(
|
|
59
|
+
kitId: string,
|
|
60
|
+
flowName: string,
|
|
61
|
+
flowId: string,
|
|
62
|
+
repoRoot: string,
|
|
63
|
+
): string | null {
|
|
64
|
+
// Primary defense: reject any slug containing traversal chars or non-identifier chars.
|
|
65
|
+
if (!SLUG_RE.test(kitId) || !SLUG_RE.test(flowName)) return null;
|
|
66
|
+
|
|
67
|
+
const override = process.env["FLOW_AGENTS_FLOW_DEFS_DIR"];
|
|
68
|
+
|
|
69
|
+
let expectedRoot: string;
|
|
70
|
+
let flowFilePath: string;
|
|
71
|
+
|
|
72
|
+
if (override) {
|
|
73
|
+
const resolvedOverride = path.resolve(override);
|
|
74
|
+
if (isAgentWritableDir(resolvedOverride)) {
|
|
75
|
+
// Override targets an agent-writable .flow-agents path — reject it and
|
|
76
|
+
// fall back to the kit root. The session will resolve the real kit flow.
|
|
77
|
+
expectedRoot = path.resolve(repoRoot, "kits");
|
|
78
|
+
flowFilePath = path.join(repoRoot, "kits", kitId, "flows", `${flowName}.flow.json`);
|
|
79
|
+
} else {
|
|
80
|
+
expectedRoot = resolvedOverride;
|
|
81
|
+
// flowId = kitId + "." + flowName; after slug validation this contains only
|
|
82
|
+
// [a-zA-Z0-9_-.] — no slashes, no traversal.
|
|
83
|
+
flowFilePath = path.join(resolvedOverride, `${flowId}.flow.json`);
|
|
84
|
+
}
|
|
85
|
+
} else {
|
|
86
|
+
expectedRoot = path.resolve(repoRoot, "kits");
|
|
87
|
+
flowFilePath = path.join(repoRoot, "kits", kitId, "flows", `${flowName}.flow.json`);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Belt-and-suspenders: confirm the resolved path stays within the expected root.
|
|
91
|
+
// After slug validation this is theoretically unreachable, but defense-in-depth
|
|
92
|
+
// verifies the invariant rather than merely asserting it.
|
|
93
|
+
const resolvedPath = path.resolve(flowFilePath);
|
|
94
|
+
if (!resolvedPath.startsWith(expectedRoot + path.sep) && resolvedPath !== expectedRoot) {
|
|
95
|
+
return null; // traversal still detected — paranoid fallback
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return resolvedPath;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// ─── Public types ─────────────────────────────────────────────────────────────
|
|
102
|
+
|
|
103
|
+
/** A single gate expectation from a FlowDefinition expects[] entry. */
|
|
104
|
+
export type GateExpectation = {
|
|
105
|
+
id: string;
|
|
106
|
+
kind: string;
|
|
107
|
+
required: boolean;
|
|
108
|
+
bundle_claim: {
|
|
109
|
+
claimType: string;
|
|
110
|
+
subjectType: string;
|
|
111
|
+
accepted_statuses: string[];
|
|
112
|
+
};
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
/** The resolved result from the active flow step. */
|
|
116
|
+
export type ActiveFlowStep = {
|
|
117
|
+
flowId: string;
|
|
118
|
+
stepId: string;
|
|
119
|
+
gateId: string;
|
|
120
|
+
gateExpects: GateExpectation[];
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
/** Shape of a gate entry in the FlowDefinition JSON. */
|
|
124
|
+
type FlowGate = {
|
|
125
|
+
step: string;
|
|
126
|
+
expects?: GateExpectation[];
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
/** Shape of a FlowDefinition JSON file. */
|
|
130
|
+
type FlowDefinition = {
|
|
131
|
+
id: string;
|
|
132
|
+
version: string;
|
|
133
|
+
/** Kit-owned phase→step mapping (ADR 0016 Abstraction A P-d). Maps lifecycle phase names
|
|
134
|
+
* (e.g. "execution") to step ids (e.g. "execute") so advance-state can write active_step_id
|
|
135
|
+
* without hardcoding any vocabulary in the core. */
|
|
136
|
+
phase_map?: Record<string, string>;
|
|
137
|
+
steps?: Array<{ id: string; next: string | null }>;
|
|
138
|
+
gates?: Record<string, FlowGate>;
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Resolve the gate expects[] for a specific (flowId, stepId) pair.
|
|
143
|
+
*
|
|
144
|
+
* @param flowId e.g. "builder.build" — kitId is extracted as the prefix before the first ".".
|
|
145
|
+
* @param stepId e.g. "verify" — matched against gate.step values in the FlowDefinition.
|
|
146
|
+
* @param repoRoot Absolute path to the repository root (kits/ lives here).
|
|
147
|
+
* Honored only when FLOW_AGENTS_FLOW_DEFS_DIR is not set.
|
|
148
|
+
* @returns ActiveFlowStep with the matched gate's expects[], or null on any error.
|
|
149
|
+
*/
|
|
150
|
+
export function resolveFlowStep(flowId: string, stepId: string, repoRoot: string): ActiveFlowStep | null {
|
|
151
|
+
if (!flowId || !stepId) return null;
|
|
152
|
+
const dotIdx = flowId.indexOf(".");
|
|
153
|
+
if (dotIdx < 1) return null; // flowId must have at least one "." to derive kitId
|
|
154
|
+
const kitId = flowId.slice(0, dotIdx);
|
|
155
|
+
// The flow filename is the part after the first "." (e.g. "build" from "builder.build")
|
|
156
|
+
const flowName = flowId.slice(dotIdx + 1);
|
|
157
|
+
if (!kitId || !flowName) return null;
|
|
158
|
+
|
|
159
|
+
// Layer 1 defense: validate stepId too — it is matched against gate.step values but
|
|
160
|
+
// still originates from agent-writable current.json active_step_id.
|
|
161
|
+
if (!SLUG_RE.test(stepId)) return null;
|
|
162
|
+
|
|
163
|
+
// Determine the FlowDefinition file path with slug validation + path containment check.
|
|
164
|
+
// Returns null for traversal attempts (e.g. flowName = "../../../.flow-agents/fake").
|
|
165
|
+
const flowFilePath = resolveFlowFilePath(kitId, flowName, flowId, repoRoot);
|
|
166
|
+
if (!flowFilePath) return null;
|
|
167
|
+
|
|
168
|
+
let flowDef: FlowDefinition;
|
|
169
|
+
try {
|
|
170
|
+
const raw = fs.readFileSync(flowFilePath, "utf8");
|
|
171
|
+
flowDef = JSON.parse(raw) as FlowDefinition;
|
|
172
|
+
} catch {
|
|
173
|
+
return null; // ENOENT, permission error, or parse error → fail-open
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (!flowDef || typeof flowDef !== "object" || !flowDef.gates) return null;
|
|
177
|
+
|
|
178
|
+
// Find the gate whose .step matches stepId.
|
|
179
|
+
for (const [gateId, gate] of Object.entries(flowDef.gates)) {
|
|
180
|
+
if (!gate || gate.step !== stepId) continue;
|
|
181
|
+
const expects = Array.isArray(gate.expects) ? gate.expects : [];
|
|
182
|
+
return { flowId, stepId, gateId, gateExpects: expects };
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return null; // no gate matched the given stepId
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Resolve the phase→step mapping from a FlowDefinition's phase_map field.
|
|
191
|
+
*
|
|
192
|
+
* Returns the phase_map object (e.g. {"execution":"execute","planning":"plan",...})
|
|
193
|
+
* or null when the flow file cannot be loaded, the phase_map field is absent, or
|
|
194
|
+
* the field is not a plain Record<string,string>.
|
|
195
|
+
*
|
|
196
|
+
* Pure and synchronous — no throws, fail-open on any error.
|
|
197
|
+
*
|
|
198
|
+
* @param flowId e.g. "builder.build" — kitId is the prefix before the first ".".
|
|
199
|
+
* @param repoRoot Absolute path to the repository root (kits/ lives here).
|
|
200
|
+
* Honored only when FLOW_AGENTS_FLOW_DEFS_DIR is not set.
|
|
201
|
+
* @returns Record<string,string> phase→stepId map, or null on absence/error.
|
|
202
|
+
*/
|
|
203
|
+
export function resolvePhaseMap(flowId: string, repoRoot: string): Record<string, string> | null {
|
|
204
|
+
if (!flowId) return null;
|
|
205
|
+
const dotIdx = flowId.indexOf(".");
|
|
206
|
+
if (dotIdx < 1) return null;
|
|
207
|
+
const kitId = flowId.slice(0, dotIdx);
|
|
208
|
+
const flowName = flowId.slice(dotIdx + 1);
|
|
209
|
+
if (!kitId || !flowName) return null;
|
|
210
|
+
|
|
211
|
+
// Layer 1 defense: same slug validation + path containment as resolveFlowStep.
|
|
212
|
+
const flowFilePath = resolveFlowFilePath(kitId, flowName, flowId, repoRoot);
|
|
213
|
+
if (!flowFilePath) return null;
|
|
214
|
+
|
|
215
|
+
let flowDef: FlowDefinition;
|
|
216
|
+
try {
|
|
217
|
+
const raw = fs.readFileSync(flowFilePath, "utf8");
|
|
218
|
+
flowDef = JSON.parse(raw) as FlowDefinition;
|
|
219
|
+
} catch {
|
|
220
|
+
return null; // ENOENT, permission error, or parse error → fail-open
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (!flowDef || typeof flowDef !== "object") return null;
|
|
224
|
+
const pm = flowDef.phase_map;
|
|
225
|
+
if (!pm || typeof pm !== "object" || Array.isArray(pm)) return null;
|
|
226
|
+
// Validate: all values must be strings
|
|
227
|
+
for (const v of Object.values(pm)) {
|
|
228
|
+
if (typeof v !== "string") return null;
|
|
229
|
+
}
|
|
230
|
+
return pm as Record<string, string>;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Find the repository root from a starting directory by walking upward to locate
|
|
235
|
+
* the nearest ancestor that contains a `kits/` subdirectory. If none is found,
|
|
236
|
+
* falls back to `process.cwd()` so the default "run from repo root" case still works.
|
|
237
|
+
*
|
|
238
|
+
* This is required because the .flow-agents directory can live anywhere (temp dirs,
|
|
239
|
+
* subprojects, CI workspaces) while the kits/ directory is always at the repo root.
|
|
240
|
+
*/
|
|
241
|
+
function findRepoRoot(startDir: string): string {
|
|
242
|
+
// Walk up from startDir looking for a kits/ directory
|
|
243
|
+
let dir = startDir;
|
|
244
|
+
for (let i = 0; i < 16; i++) {
|
|
245
|
+
if (fs.existsSync(path.join(dir, "kits"))) return dir;
|
|
246
|
+
const parent = path.dirname(dir);
|
|
247
|
+
if (parent === dir) break; // reached filesystem root
|
|
248
|
+
dir = parent;
|
|
249
|
+
}
|
|
250
|
+
// Fallback: process.cwd() covers the common "run from repo root" case
|
|
251
|
+
return process.cwd();
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Resolve the active flow step from current.json.
|
|
256
|
+
*
|
|
257
|
+
* Reads active_flow_id and active_step_id from <flowAgentsDir>/current.json.
|
|
258
|
+
* If both are present, delegates to resolveFlowStep. The repoRoot is derived by
|
|
259
|
+
* walking upward from flowAgentsDir to find the nearest ancestor containing kits/,
|
|
260
|
+
* with a fallback to process.cwd(). This handles temp dirs, CI workspaces, and
|
|
261
|
+
* subproject layouts without hardcoding the repo structure.
|
|
262
|
+
*
|
|
263
|
+
* @param flowAgentsDir Path to the .flow-agents directory (contains current.json).
|
|
264
|
+
* @returns ActiveFlowStep or null when fields are absent or resolution fails.
|
|
265
|
+
*/
|
|
266
|
+
export function resolveActiveFlowStep(flowAgentsDir: string): ActiveFlowStep | null {
|
|
267
|
+
if (!flowAgentsDir) return null;
|
|
268
|
+
const currentFile = path.join(flowAgentsDir, "current.json");
|
|
269
|
+
let current: Record<string, unknown>;
|
|
270
|
+
try {
|
|
271
|
+
const raw = fs.readFileSync(currentFile, "utf8");
|
|
272
|
+
current = JSON.parse(raw) as Record<string, unknown>;
|
|
273
|
+
} catch {
|
|
274
|
+
return null;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const flowId = typeof current["active_flow_id"] === "string" ? current["active_flow_id"] : null;
|
|
278
|
+
const stepId = typeof current["active_step_id"] === "string" ? current["active_step_id"] : null;
|
|
279
|
+
if (!flowId || !stepId) return null;
|
|
280
|
+
|
|
281
|
+
// Find repoRoot: walk up from flowAgentsDir to find kits/, fallback to cwd
|
|
282
|
+
const repoRoot = findRepoRoot(path.dirname(flowAgentsDir));
|
|
283
|
+
return resolveFlowStep(flowId, stepId, repoRoot);
|
|
284
|
+
}
|
|
@@ -7,7 +7,7 @@ import { loadJson, readText, root, walkFiles, writeText } from "./common.js";
|
|
|
7
7
|
type Agent = Record<string, unknown> & { name: string; prompt: string };
|
|
8
8
|
const dist = process.env.FLOW_AGENTS_DIST_DIR ? path.resolve(process.env.FLOW_AGENTS_DIST_DIR) : path.join(root, "dist");
|
|
9
9
|
const manifest = loadJson<Record<string, any>>(path.join(root, "packaging/manifest.json"));
|
|
10
|
-
const
|
|
10
|
+
const pkgVersion: string = (loadJson<Record<string, unknown>>(path.join(root, "package.json")) as Record<string, string>)["version"] ?? "0.0.0";
|
|
11
11
|
const textExtensions = new Set([".css", ".html", ".js", ".json", ".md", ".sh", ".toml", ".txt", ".yaml", ".yml", ".ts"]);
|
|
12
12
|
const dropDiagnostics: string[] = [];
|
|
13
13
|
const printDiagnostics = !["0", "false", "no"].includes(String(process.env.FLOW_AGENTS_EXPORT_DIAGNOSTICS ?? "1").toLowerCase());
|
|
@@ -181,8 +181,9 @@ function generatedAgentsSummary(agents: Agent[]): string {
|
|
|
181
181
|
function exportRootAgentsMd(label: string, agents: Agent[], taskDir: string): string {
|
|
182
182
|
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`;
|
|
183
183
|
}
|
|
184
|
-
|
|
185
|
-
|
|
184
|
+
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`;
|
|
185
|
+
function exportTargetReadme(label: string, installHint: string, extra = ""): string {
|
|
186
|
+
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}`;
|
|
186
187
|
}
|
|
187
188
|
|
|
188
189
|
function mapClaudeTools(allowedTools: unknown): string[] {
|
|
@@ -255,8 +256,8 @@ function shellHook(command: string, timeout = 10, statusMessage?: string): Recor
|
|
|
255
256
|
function claudeTelemetry(event: string): string {
|
|
256
257
|
return `bash -lc 'root="\${CLAUDE_PROJECT_DIR:-$(pwd)}"; node "$root/scripts/hooks/claude-telemetry-hook.js" ${event} dev'`;
|
|
257
258
|
}
|
|
258
|
-
function claudePolicy(event: string, script: string): string {
|
|
259
|
-
return `bash -lc 'root="\${CLAUDE_PROJECT_DIR:-$(pwd)}"; node "$root/scripts/hooks/claude-hook-adapter.js" ${event} ${script.replace(/\.js$/, "")} ${script} default'`;
|
|
259
|
+
function claudePolicy(event: string, script: string, envPrefix = ""): string {
|
|
260
|
+
return `bash -lc 'root="\${CLAUDE_PROJECT_DIR:-$(pwd)}"; ${envPrefix}node "$root/scripts/hooks/claude-hook-adapter.js" ${event} ${script.replace(/\.js$/, "")} ${script} default'`;
|
|
260
261
|
}
|
|
261
262
|
function codexRoot(scriptPath: string): string {
|
|
262
263
|
return `root="\${CODEX_HOME:-}"; if [ -z "$root" ] || [ ! -f "$root/${scriptPath}" ]; then root=$(git rev-parse --show-toplevel 2>/dev/null || pwd); fi`;
|
|
@@ -265,17 +266,23 @@ function codexTelemetry(event: string): string {
|
|
|
265
266
|
if (event === "PermissionRequest") return `bash -lc '${codexRoot("scripts/telemetry/telemetry.sh")}; bash "$root/scripts/telemetry/telemetry.sh" permissionRequest dev'`;
|
|
266
267
|
return `bash -lc '${codexRoot("scripts/hooks/codex-telemetry-hook.js")}; node "$root/scripts/hooks/codex-telemetry-hook.js" ${event} dev'`;
|
|
267
268
|
}
|
|
268
|
-
function codexPolicy(script: string): string {
|
|
269
|
-
return `bash -lc '${codexRoot("scripts/hooks/codex-hook-adapter.js")}; node "$root/scripts/hooks/codex-hook-adapter.js" ${script.replace(/\.js$/, "")} ${script} default'`;
|
|
269
|
+
function codexPolicy(script: string, envPrefix = ""): string {
|
|
270
|
+
return `bash -lc '${codexRoot("scripts/hooks/codex-hook-adapter.js")}; ${envPrefix}node "$root/scripts/hooks/codex-hook-adapter.js" ${script.replace(/\.js$/, "")} ${script} default'`;
|
|
270
271
|
}
|
|
272
|
+
// Shipped L2 runtimes enforce goal fit by default (mode=block), while remaining
|
|
273
|
+
// operator-overridable via the FLOW_AGENTS_GOAL_FIT_MODE environment variable.
|
|
274
|
+
// The canonical engine default stays warn so the conformance contract is honest.
|
|
275
|
+
const GOAL_FIT_MODE_PREFIX = 'FLOW_AGENTS_GOAL_FIT_MODE="${FLOW_AGENTS_GOAL_FIT_MODE:-block}" ';
|
|
271
276
|
function exportClaudeSettings(): string {
|
|
272
277
|
const hooks: Record<string, Array<Record<string, unknown>>> = {};
|
|
273
278
|
for (const event of ["SessionStart", "UserPromptSubmit", "PreToolUse", "PermissionRequest", "PostToolUse", "Stop", "SessionEnd"]) {
|
|
274
279
|
hooks[event] = [{ hooks: [shellHook(claudeTelemetry(event), 10, "Recording Flow Agents telemetry")] }];
|
|
275
280
|
}
|
|
276
|
-
hooks.Stop.push({ hooks: [shellHook(claudePolicy("Stop", "stop-goal-fit.js"), 30, "Running Flow Agents hook policy")] });
|
|
281
|
+
hooks.Stop.push({ hooks: [shellHook(claudePolicy("Stop", "stop-goal-fit.js", GOAL_FIT_MODE_PREFIX), 30, "Running Flow Agents hook policy")] });
|
|
282
|
+
hooks.SessionStart.push({ hooks: [shellHook(claudePolicy("SessionStart", "workflow-steering.js"), 30, "Running Flow Agents hook policy")] });
|
|
277
283
|
hooks.UserPromptSubmit.push({ hooks: [shellHook(claudePolicy("UserPromptSubmit", "workflow-steering.js"), 30, "Running Flow Agents hook policy")] });
|
|
278
284
|
hooks.PostToolUse.push({ hooks: [shellHook(claudePolicy("PostToolUse", "quality-gate.js"), 30, "Running Flow Agents hook policy")] });
|
|
285
|
+
hooks.PostToolUse.push({ hooks: [shellHook(claudePolicy("PostToolUse", "evidence-capture.js"), 30, "Capturing Flow Agents command evidence")] });
|
|
279
286
|
hooks.PreToolUse.push({ hooks: [shellHook(claudePolicy("PreToolUse", "config-protection.js"), 30, "Running Flow Agents hook policy")] });
|
|
280
287
|
return `${JSON.stringify({
|
|
281
288
|
statusLine: { type: "command", command: 'bash -lc \'root="${CLAUDE_PROJECT_DIR:-$(pwd)}"; node "$root/scripts/statusline/flow-agents-statusline.js"\'' },
|
|
@@ -289,8 +296,10 @@ function exportCodexHooks(): string {
|
|
|
289
296
|
for (const event of ["SessionStart", "UserPromptSubmit", "PreToolUse", "PermissionRequest", "PostToolUse", "Stop"]) {
|
|
290
297
|
hooks[event] = [{ hooks: [shellHook(codexTelemetry(event), 10, "Recording Flow Agents telemetry")] }];
|
|
291
298
|
}
|
|
292
|
-
hooks.Stop.push({ hooks: [shellHook(codexPolicy("stop-goal-fit.js"), 30, "Running Flow Agents hook policy")] });
|
|
299
|
+
hooks.Stop.push({ hooks: [shellHook(codexPolicy("stop-goal-fit.js", GOAL_FIT_MODE_PREFIX), 30, "Running Flow Agents hook policy")] });
|
|
300
|
+
hooks.SessionStart.push({ hooks: [shellHook(codexPolicy("workflow-steering.js"), 30, "Running Flow Agents hook policy")] });
|
|
293
301
|
hooks.UserPromptSubmit.push({ hooks: [shellHook(codexPolicy("workflow-steering.js"), 30, "Running Flow Agents hook policy")] });
|
|
302
|
+
hooks.PostToolUse.push({ hooks: [shellHook(codexPolicy("evidence-capture.js"), 30, "Capturing Flow Agents command evidence")] });
|
|
294
303
|
return `${JSON.stringify({ hooks }, null, 2)}\n`;
|
|
295
304
|
}
|
|
296
305
|
|
|
@@ -310,19 +319,22 @@ function copySharedContent(targetRoot: string, targetName: string, token: string
|
|
|
310
319
|
}
|
|
311
320
|
for (const dir of manifest.optional_copy_dirs ?? []) copyTree(path.join(root, dir), path.join(targetRoot, dir), targetName, token);
|
|
312
321
|
writeText(path.join(targetRoot, "build/package.json"), `${JSON.stringify({ type: "module" }, null, 2)}\n`);
|
|
313
|
-
const filterBuilt = path.join(root, "build/src/tools/filter-installed-packs.js");
|
|
314
322
|
const commonBuilt = path.join(root, "build/src/tools/common.js");
|
|
315
|
-
if (fs.existsSync(filterBuilt)) writeText(path.join(targetRoot, "scripts/filter-installed-packs.mjs"), readText(filterBuilt).replace("./common.js", "./common.mjs"));
|
|
316
323
|
if (fs.existsSync(commonBuilt)) writeText(path.join(targetRoot, "scripts/common.mjs"), readText(commonBuilt));
|
|
317
324
|
copyTree(path.join(root, "build/src"), path.join(targetRoot, "build/src"), targetName, token);
|
|
318
325
|
}
|
|
319
|
-
function installScript(label: string, defaultDestDisplay: string, token?: string, destFallbackShell?: string): string {
|
|
326
|
+
function installScript(label: string, defaultDestDisplay: string, token?: string, destFallbackShell?: string, mergeConfig?: { configRelPath: string; managedConfigRelPath: string; runtime: string; version: string }, stampConfig?: { runtime: string; version: string }): string {
|
|
320
327
|
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'` : "";
|
|
321
328
|
const destFallback = destFallbackShell ? `\nif [[ -z "$DEST" ]]; then\n DEST="${destFallbackShell}"\nfi` : "";
|
|
322
329
|
const destRequired = !destFallbackShell;
|
|
323
330
|
const requiredCheck = destRequired ? `if [[ -z "$DEST" ]]; then\n usage\n exit 2\nfi\n` : "";
|
|
324
331
|
const usageDest = destRequired ? "/path/to/workspace" : defaultDestDisplay;
|
|
325
|
-
|
|
332
|
+
const mergeBlock = mergeConfig
|
|
333
|
+
? `\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`
|
|
334
|
+
: stampConfig
|
|
335
|
+
? `\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`
|
|
336
|
+
: "";
|
|
337
|
+
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`;
|
|
326
338
|
}
|
|
327
339
|
|
|
328
340
|
function buildBase(agents: Agent[]): void {
|
|
@@ -332,7 +344,7 @@ function buildBase(agents: Agent[]): void {
|
|
|
332
344
|
writeText(path.join(bundle, ".flow-agents", ".gitkeep"), "");
|
|
333
345
|
writeText(path.join(bundle, "AGENTS.md"), exportRootAgentsMd("Base", agents, ".flow-agents"));
|
|
334
346
|
writeText(path.join(bundle, "README.md"), exportTargetReadme("Base", "bash install.sh /path/to/workspace"));
|
|
335
|
-
writeText(path.join(bundle, "install.sh"), installScript("Base", "/path/to/workspace"));
|
|
347
|
+
writeText(path.join(bundle, "install.sh"), installScript("Base", "/path/to/workspace", undefined, undefined, undefined, { runtime: "base", version: pkgVersion }));
|
|
336
348
|
fs.chmodSync(path.join(bundle, "install.sh"), 0o755);
|
|
337
349
|
}
|
|
338
350
|
|
|
@@ -344,7 +356,7 @@ function buildKiro(agents: Agent[]): void {
|
|
|
344
356
|
for (const spec of agents) writeText(path.join(bundle, "agents", `${spec.name}.json`), sanitizeText(`${JSON.stringify(sanitizeAgentJson(spec), null, 2)}\n`, "kiro", token));
|
|
345
357
|
writeText(path.join(bundle, "AGENTS.md"), exportRootAgentsMd("Kiro", agents, ".flow-agents"));
|
|
346
358
|
writeText(path.join(bundle, "README.md"), exportTargetReadme("Kiro", "bash install.sh $HOME/.flow-agents"));
|
|
347
|
-
writeText(path.join(bundle, "install.sh"), installScript("Kiro", "$HOME/.flow-agents", token, '${FLOW_AGENTS_DEST:-$HOME/.flow-agents}'));
|
|
359
|
+
writeText(path.join(bundle, "install.sh"), installScript("Kiro", "$HOME/.flow-agents", token, '${FLOW_AGENTS_DEST:-$HOME/.flow-agents}', undefined, { runtime: "kiro", version: pkgVersion }));
|
|
348
360
|
fs.chmodSync(path.join(bundle, "install.sh"), 0o755);
|
|
349
361
|
}
|
|
350
362
|
function buildClaudeCode(agents: Agent[]): void {
|
|
@@ -359,7 +371,7 @@ function buildClaudeCode(agents: Agent[]): void {
|
|
|
359
371
|
writeText(path.join(bundle, ".claude/settings.json"), exportClaudeSettings());
|
|
360
372
|
writeText(path.join(bundle, "AGENTS.md"), exportRootAgentsMd("Claude Code", agents, manifest.claude_code.task_dir));
|
|
361
373
|
writeText(path.join(bundle, "README.md"), exportTargetReadme("Claude Code", "bash install.sh /path/to/workspace"));
|
|
362
|
-
writeText(path.join(bundle, "install.sh"), installScript("Claude Code", "/path/to/workspace"));
|
|
374
|
+
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 }));
|
|
363
375
|
fs.chmodSync(path.join(bundle, "install.sh"), 0o755);
|
|
364
376
|
}
|
|
365
377
|
function buildCodex(agents: Agent[]): void {
|
|
@@ -378,8 +390,8 @@ function buildCodex(agents: Agent[]): void {
|
|
|
378
390
|
writeText(path.join(bundle, ".codex/skills", name, "SKILL.md"), sanitizeText(readText(src), "codex", "<bundle-root>"));
|
|
379
391
|
}
|
|
380
392
|
writeText(path.join(bundle, "AGENTS.md"), exportRootAgentsMd("Codex", targetAgents, manifest.codex.task_dir));
|
|
381
|
-
writeText(path.join(bundle, "README.md"), exportTargetReadme("Codex", "bash install.sh /path/to/workspace"));
|
|
382
|
-
writeText(path.join(bundle, "install.sh"), installScript("Codex", "/path/to/workspace"));
|
|
393
|
+
writeText(path.join(bundle, "README.md"), exportTargetReadme("Codex", "bash install.sh /path/to/workspace", CODEX_LIVE_HOOKS_README));
|
|
394
|
+
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 }));
|
|
383
395
|
fs.chmodSync(path.join(bundle, "install.sh"), 0o755);
|
|
384
396
|
}
|
|
385
397
|
function exportOpencodeAgent(spec: Agent): string {
|
|
@@ -515,6 +527,7 @@ export const FlowAgentsPlugin = async ({ project, client, $, directory, worktree
|
|
|
515
527
|
const detail = { tool: input && input.tool };
|
|
516
528
|
runTelemetry('tool.execute.after', detail);
|
|
517
529
|
runAdapter('opencode-hook-adapter.js', 'tool.execute.after', detail, 'quality-gate', 'quality-gate.js', 'default');
|
|
530
|
+
runAdapter('opencode-hook-adapter.js', 'tool.execute.after', detail, 'evidence-capture', 'evidence-capture.js', 'default');
|
|
518
531
|
},
|
|
519
532
|
'session.idle': async (_input, _output) => {
|
|
520
533
|
runTelemetry('session.idle');
|
|
@@ -560,7 +573,7 @@ function buildOpencode(agents: Agent[]): void {
|
|
|
560
573
|
writeText(path.join(bundle, "opencode.json"), exportOpencodeConfig());
|
|
561
574
|
writeText(path.join(bundle, "AGENTS.md"), exportRootAgentsMd("opencode", agents, manifest.opencode.task_dir));
|
|
562
575
|
writeText(path.join(bundle, "README.md"), exportTargetReadme("opencode", "bash install.sh /path/to/workspace"));
|
|
563
|
-
writeText(path.join(bundle, "install.sh"), installScript("opencode", "/path/to/workspace"));
|
|
576
|
+
writeText(path.join(bundle, "install.sh"), installScript("opencode", "/path/to/workspace", undefined, undefined, { configRelPath: "opencode.json", managedConfigRelPath: "opencode.json", runtime: "opencode", version: pkgVersion }));
|
|
564
577
|
fs.chmodSync(path.join(bundle, "install.sh"), 0o755);
|
|
565
578
|
}
|
|
566
579
|
function exportPiExtension(): string {
|
|
@@ -648,6 +661,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
648
661
|
pi.on("tool_result", async (_event, _ctx) => {
|
|
649
662
|
runTelemetry("tool_result");
|
|
650
663
|
runAdapter("pi-hook-adapter.js", "tool_result", "quality-gate", "quality-gate.js");
|
|
664
|
+
runAdapter("pi-hook-adapter.js", "tool_result", "evidence-capture", "evidence-capture.js");
|
|
651
665
|
});
|
|
652
666
|
|
|
653
667
|
pi.on("session_shutdown", async (_event, _ctx) => {
|
|
@@ -670,7 +684,7 @@ function buildPi(agents: Agent[]): void {
|
|
|
670
684
|
writeText(path.join(bundle, ".pi/extensions/flow-agents.ts"), exportPiExtension());
|
|
671
685
|
writeText(path.join(bundle, "AGENTS.md"), exportRootAgentsMd("pi", agents, manifest.pi.task_dir));
|
|
672
686
|
writeText(path.join(bundle, "README.md"), exportTargetReadme("pi", "bash install.sh /path/to/workspace"));
|
|
673
|
-
writeText(path.join(bundle, "install.sh"), installScript("pi", "/path/to/workspace"));
|
|
687
|
+
writeText(path.join(bundle, "install.sh"), installScript("pi", "/path/to/workspace", undefined, undefined, undefined, { runtime: "pi", version: pkgVersion }));
|
|
674
688
|
fs.chmodSync(path.join(bundle, "install.sh"), 0o755);
|
|
675
689
|
}
|
|
676
690
|
function buildCatalog(agents: Agent[]): Record<string, unknown> {
|
|
@@ -680,7 +694,6 @@ function buildCatalog(agents: Agent[]): Record<string, unknown> {
|
|
|
680
694
|
agents: agents.slice().sort((a, b) => a.name.localeCompare(b.name)).map((spec) => spec.name),
|
|
681
695
|
skills: collectAllSkills().map(({ name }) => name),
|
|
682
696
|
powers: fs.readdirSync(path.join(root, "powers")).filter((name) => fs.existsSync(path.join(root, "powers", name, "mcp.json"))).sort(),
|
|
683
|
-
packs: packs.packs ?? [],
|
|
684
697
|
kits: fs.existsSync(kitsCatalog) ? loadJson<Record<string, unknown>>(kitsCatalog).kits ?? [] : [],
|
|
685
698
|
};
|
|
686
699
|
}
|
|
@@ -11,19 +11,20 @@ const dirDescriptions: Record<string, string> = {
|
|
|
11
11
|
context: "Shared contracts, routing notes, templates, and reusable guidance.",
|
|
12
12
|
docs: "Long-lived project documentation and GitHub Pages content.",
|
|
13
13
|
evals: "Static, integration, install, and behavioral eval fixtures.",
|
|
14
|
-
powers: "Optional MCP/tool
|
|
14
|
+
powers: "Optional MCP/tool capability bundles.",
|
|
15
15
|
prompts: "Reusable prompt entry points.",
|
|
16
16
|
schemas: "JSON Schema contracts for machine-readable workflow artifacts.",
|
|
17
17
|
scripts: "Build, validation, hook, telemetry, workflow, and import/export utilities.",
|
|
18
18
|
skills: "On-demand capability instructions and workflow primitives.",
|
|
19
19
|
};
|
|
20
|
-
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"]);
|
|
20
|
+
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"]);
|
|
21
21
|
const commands = [
|
|
22
22
|
["Source tree", "npm run validate:source"],
|
|
23
23
|
["Static suite", "bash evals/run.sh static"],
|
|
24
24
|
["Integration suite", "bash evals/run.sh integration"],
|
|
25
25
|
["Workflow artifacts", "npm run workflow:validate-artifacts -- --require-sidecars --require-critique .flow-agents/<slug>"],
|
|
26
26
|
["Workflow sidecars", "npm run workflow:sidecar -- --help"],
|
|
27
|
+
["Claim lookup", "npm run workflow:sidecar -- claim <id> <dir>"],
|
|
27
28
|
["Context map drift", "npm run context-map:check"],
|
|
28
29
|
["Bundle build", "npm run build:bundles"],
|
|
29
30
|
];
|
|
@@ -142,18 +143,6 @@ function powers(): string[][] {
|
|
|
142
143
|
return fs.readdirSync(dir).sort().flatMap((name) => exists(path.join(dir, name, "POWER.md")) ? [[name, rel(path.join(dir, name, "POWER.md"))]] : []);
|
|
143
144
|
}
|
|
144
145
|
|
|
145
|
-
function packs(): string[][] {
|
|
146
|
-
const data = loadJson<{ packs?: Array<Record<string, unknown>> }>(path.join(root, "packaging/packs.json"));
|
|
147
|
-
return (data.packs ?? []).map((pack) => [
|
|
148
|
-
String(pack.name ?? ""),
|
|
149
|
-
pack.default ? "yes" : "no",
|
|
150
|
-
String(Array.isArray(pack.skills) ? pack.skills.length : 0),
|
|
151
|
-
String(Array.isArray(pack.agents) ? pack.agents.length : 0),
|
|
152
|
-
String(Array.isArray(pack.powers) ? pack.powers.length : 0),
|
|
153
|
-
oneLine(String(pack.description ?? "")),
|
|
154
|
-
]);
|
|
155
|
-
}
|
|
156
|
-
|
|
157
146
|
function latestRuntimeStates(includeRuntime: boolean): string[] {
|
|
158
147
|
if (!includeRuntime) {
|
|
159
148
|
return [
|
|
@@ -193,9 +182,6 @@ function render(includeRuntime: boolean): string {
|
|
|
193
182
|
"## Support Skills", "", ...markdownTable(["Skill", "Source", "When To Load"], supportRows), "",
|
|
194
183
|
"## Agents", "", ...markdownTable(["Agent", "Model", "Tools", "Role"], agents()), "",
|
|
195
184
|
"## Optional Powers", "", ...markdownTable(["Power", "Source"], powers()), "",
|
|
196
|
-
"## Packs", "",
|
|
197
|
-
"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.", "",
|
|
198
|
-
...markdownTable(["Pack", "Default", "Skills", "Agents", "Powers", "Purpose"], packs()), "",
|
|
199
185
|
"## Current Workflow State", "", ...latestRuntimeStates(includeRuntime), "",
|
|
200
186
|
"## Context Loading Rules", "",
|
|
201
187
|
"- For delivery work, load `deliver`, then the specific primitive skill for the current phase.",
|