@kontourai/flow-agents 2.3.0 → 3.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 +16 -0
- package/.github/workflows/ci.yml +44 -0
- package/.github/workflows/trust-reconcile.yml +62 -4
- package/AGENTS.md +4 -4
- package/CHANGELOG.md +62 -0
- package/CONTEXT.md +35 -0
- package/README.md +9 -3
- package/agents/dev.json +1 -1
- package/agents/tool-code-reviewer.json +1 -1
- package/agents/tool-planner.json +3 -3
- package/agents/tool-verifier.json +3 -3
- package/build/src/cli/assignment-provider.d.ts +1 -0
- package/build/src/cli/assignment-provider.js +748 -0
- package/build/src/cli/console-learning-projection.js +3 -2
- package/build/src/cli/effective-assignment-provider-settings.d.ts +1 -0
- package/build/src/cli/effective-assignment-provider-settings.js +125 -0
- package/build/src/cli/init.js +104 -21
- package/build/src/cli/kit.js +37 -6
- package/build/src/cli/pull-work-provider.js +1 -1
- package/build/src/cli/usage-feedback.js +3 -3
- package/build/src/cli/validate-hook-influence.js +1 -0
- package/build/src/cli/validate-workflow-artifacts.js +27 -7
- package/build/src/cli/workflow-artifact-cleanup-audit.js +36 -2
- package/build/src/cli/workflow-sidecar.d.ts +14 -7
- package/build/src/cli/workflow-sidecar.js +938 -168
- package/build/src/cli.js +6 -0
- package/build/src/flow-kit/validate.d.ts +23 -0
- package/build/src/flow-kit/validate.js +67 -1
- package/build/src/index.d.ts +1 -1
- package/build/src/index.js +1 -1
- package/build/src/lib/flow-resolver.d.ts +6 -4
- package/build/src/lib/flow-resolver.js +125 -58
- package/build/src/lib/local-artifact-root.d.ts +14 -2
- package/build/src/lib/local-artifact-root.js +22 -5
- package/build/src/lib/workflow-learning-projection.js +2 -2
- package/build/src/runtime-adapters.d.ts +12 -0
- package/build/src/runtime-adapters.js +56 -16
- package/build/src/tools/build-universal-bundles.js +49 -16
- package/build/src/tools/generate-context-map.js +10 -8
- package/build/src/tools/validate-source-tree.js +24 -1
- package/console.telemetry.json +22 -0
- package/context/contracts/artifact-contract.md +40 -7
- package/context/contracts/assignment-provider-contract.md +239 -0
- package/context/contracts/builder-kit-workflow-state-contract.md +5 -2
- package/context/contracts/decision-registry-contract.md +265 -0
- package/context/contracts/delivery-contract.md +3 -1
- package/context/contracts/execution-contract.md +25 -0
- package/context/contracts/governance-adapter-contract.md +2 -0
- package/context/contracts/knowledge-store-contract.md +197 -0
- package/context/contracts/planning-contract.md +4 -1
- package/context/contracts/review-contract.md +7 -3
- package/context/contracts/sandbox-policy.md +2 -0
- package/context/contracts/standing-directives.md +13 -0
- package/context/contracts/verification-contract.md +11 -1
- package/context/contracts/work-item-contract.md +3 -1
- package/context/deferred/parallelization.md +1 -1
- package/context/gate-awareness.md +4 -4
- package/context/scripts/git-status.sh +2 -2
- package/context/scripts/hooks/config-protection.js +502 -2
- package/context/scripts/hooks/lib/config-protection-remedies.js +69 -0
- package/context/scripts/hooks/run-hook.js +14 -1
- package/context/scripts/hooks/stop-goal-fit.js +1140 -99
- package/context/scripts/hooks/workflow-steering.js +212 -6
- package/context/settings/assignment-provider-settings.json +33 -0
- package/docs/adr/0007-flow-skill-kit-tool-boundary.md +2 -0
- package/docs/adr/0015-flow-flow-agents-boundary-reconciliation.md +35 -0
- package/docs/adr/0019-kit-dependency-ownership.md +62 -0
- package/docs/adr/0020-trust-reconcile-manifest-and-claim-classification.md +290 -0
- package/docs/adr/0021-assignment-leases-and-stale-claim-takeover.md +221 -0
- package/docs/adr/0022-fail-closed-delivery-reconciliation-with-governed-exemptions.md +463 -0
- package/docs/agent-system-guidebook.md +3 -3
- package/docs/agent-usage-feedback-loop.md +6 -6
- package/docs/context-map.md +10 -7
- package/docs/decisions/decision-records.md +57 -0
- package/docs/decisions/index.md +17 -0
- package/docs/decisions/knowledge-store-provider.md +51 -0
- package/docs/decisions/model-routing.md +63 -0
- package/docs/decisions/promotion-gate.md +52 -0
- package/docs/decisions/standing-directives.md +66 -0
- package/docs/developer-architecture.md +1 -1
- package/docs/fixture-ownership.md +4 -0
- package/docs/flow-kit-repository-contract.md +5 -5
- package/docs/getting-started.md +12 -2
- package/docs/integrations/harness-install.md +2 -1
- package/docs/kit-authoring-guide.md +64 -5
- package/docs/knowledge-kit.md +5 -1
- package/docs/learnings/2026-07-improvement-program.md +203 -0
- package/docs/migrations.md +6 -5
- package/docs/north-star.md +2 -2
- package/docs/operating-layers.md +4 -3
- package/docs/repository-structure.md +5 -5
- package/docs/skills-map.md +8 -8
- package/docs/spec/runtime-hook-surface.md +19 -15
- package/docs/spikes/graph-provider-2026-07.md +155 -0
- package/docs/standards-register.md +7 -7
- package/docs/trust-anchor-adoption.md +105 -4
- package/docs/work-item-adapters.md +1 -1
- package/docs/workflow-artifact-lifecycle.md +72 -9
- package/docs/workflow-eval-strategy.md +3 -3
- package/docs/workflow-shared-contracts.md +3 -2
- package/docs/workflow-usage-guide.md +260 -21
- package/evals/acceptance/DEMO-false-completion.md +20 -11
- package/evals/acceptance/prove-capture-teeth-declared.sh +15 -15
- package/evals/acceptance/prove-capture-teeth.sh +11 -11
- package/evals/acceptance/test_kiro_harness.sh +1 -1
- package/evals/ci/antigaming-suite.sh +5 -0
- package/evals/ci/run-baseline.sh +71 -1
- package/evals/fixtures/assignment-provider/actor-a.json +6 -0
- package/evals/fixtures/assignment-provider/actor-b.json +6 -0
- package/evals/fixtures/assignment-provider/github-issue-claimed.json +27 -0
- package/evals/fixtures/assignment-provider/github-issue-unassigned.json +7 -0
- package/evals/fixtures/assignment-provider/liveness-fresh.json +9 -0
- package/evals/fixtures/assignment-provider/liveness-stale.json +9 -0
- package/evals/fixtures/flow-kit-repository/invalid-bad-dependency/flows/review.flow.json +26 -0
- package/evals/fixtures/flow-kit-repository/invalid-bad-dependency/kit.json +13 -0
- package/evals/fixtures/flow-kit-repository/valid-with-dependency/flows/review.flow.json +26 -0
- package/evals/fixtures/flow-kit-repository/valid-with-dependency/kit.json +13 -0
- package/evals/fixtures/hook-influence/cases.json +32 -0
- package/evals/fixtures/pull-work-provider/github-issues.json +6 -6
- package/evals/fixtures/trust-reconcile-exploits/fabricated-attestation.json +59 -0
- package/evals/fixtures/trust-reconcile-exploits/no-label-bypass.json +36 -0
- package/evals/fixtures/trust-reconcile-exploits/skip-assumed-bypass.json +59 -0
- package/evals/fixtures/trust-reconcile-exploits/status-misassertion.json +48 -0
- package/evals/fixtures/trust-reconcile-exploits/waived-command-check.json +44 -0
- package/evals/fixtures/trust-reconcile-mixed-bundle/mixed-bundle.json +159 -0
- package/evals/fixtures/trust-reconcile-ws3/ws3-bundle.json +920 -0
- package/evals/integration/test_actor_identity.sh +254 -0
- package/evals/integration/test_assignment_provider_github.sh +318 -0
- package/evals/integration/test_assignment_provider_local_file.sh +222 -0
- package/evals/integration/test_builder_step_producers.sh +33 -23
- package/evals/integration/test_bundle_install.sh +22 -16
- package/evals/integration/test_bundle_lifecycle.sh +11 -11
- package/evals/integration/test_captured_fail_reconciliation.sh +50 -50
- package/evals/integration/test_checkpoint_signing.sh +43 -0
- package/evals/integration/test_claim_lookup.sh +4 -4
- package/evals/integration/test_codex_hook_resolution.sh +114 -0
- package/evals/integration/test_command_log_concurrency.sh +8 -8
- package/evals/integration/test_command_log_fork_classification.sh +3 -3
- package/evals/integration/test_command_log_integrity.sh +16 -16
- package/evals/integration/test_console_learning_projection.sh +1 -1
- package/evals/integration/test_critique_supersession_roundtrip.sh +182 -0
- package/evals/integration/test_dual_emit_flow_step.sh +145 -0
- package/evals/integration/test_enforcer_expects_driven.sh +12 -12
- package/evals/integration/test_evidence_capture_hook.sh +14 -13
- package/evals/integration/test_fixture_retirement_audit.sh +2 -2
- package/evals/integration/test_flow_agents_statusline.sh +2 -2
- package/evals/integration/test_flow_kit_install_git.sh +52 -0
- package/evals/integration/test_flow_kit_repository.sh +2 -0
- package/evals/integration/test_flowdef_session_activation.sh +6 -6
- package/evals/integration/test_flowdef_union_floor_regression.sh +429 -0
- package/evals/integration/test_gate_bypass_chain.sh +52 -40
- package/evals/integration/test_gate_lockdown.sh +97 -73
- package/evals/integration/test_gate_review_inquiry_records.sh +3 -3
- package/evals/integration/test_goal_fit_escape_hatch.sh +10 -10
- package/evals/integration/test_goal_fit_ghost_session.sh +104 -0
- package/evals/integration/test_goal_fit_hook.sh +32 -32
- package/evals/integration/test_goal_fit_rederive.sh +9 -9
- package/evals/integration/test_hook_category_behaviors.sh +103 -0
- package/evals/integration/test_hook_influence_cases.sh +1 -0
- package/evals/integration/test_install_merge.sh +262 -1
- package/evals/integration/test_kit_identity_trust.sh +6 -6
- package/evals/integration/test_liveness_conflict_injection.sh +587 -0
- package/evals/integration/test_liveness_heartbeat.sh +677 -0
- package/evals/integration/test_liveness_verdict.sh +394 -0
- package/evals/integration/test_local_flow_kit_install.sh +15 -0
- package/evals/integration/test_phase_map_and_gate_claim.sh +63 -11
- package/evals/integration/test_promote_gate.sh +178 -0
- package/evals/integration/test_publish_delivery.sh +23 -6
- package/evals/integration/test_pull_work_assignment_join.sh +132 -0
- package/evals/integration/test_pull_work_liveness_preflight.sh +394 -0
- package/evals/integration/test_pull_work_provider.sh +2 -2
- package/evals/integration/test_reconcile_soundness.sh +37 -13
- package/evals/integration/test_resolvefirststep_security.sh +22 -0
- package/evals/integration/test_runtime_adapter_activation.sh +87 -18
- package/evals/integration/test_session_resume_roundtrip.sh +147 -0
- package/evals/integration/test_sidecar_field_preservation.sh +249 -0
- package/evals/integration/test_trust_reconcile.sh +11 -10
- package/evals/integration/test_trust_reconcile_manifest.sh +79 -0
- package/evals/integration/test_trust_reconcile_mixed_bundle.sh +96 -0
- package/evals/integration/test_trust_reconcile_negatives.sh +759 -0
- package/evals/integration/test_usage_feedback_global.sh +5 -5
- package/evals/integration/test_validate_artifacts_portability.sh +60 -0
- package/evals/integration/test_verify_cli.sh +2 -2
- package/evals/integration/test_veritas_governance_kit.sh +117 -0
- package/evals/integration/test_workflow_artifact_cleanup_audit.sh +26 -1
- package/evals/integration/test_workflow_artifacts.sh +33 -0
- package/evals/integration/test_workflow_sidecar_writer.sh +802 -74
- package/evals/integration/test_workflow_steering_hook.sh +175 -5
- package/evals/lib/codex-provider.sh +1 -1
- package/evals/lib/node.sh +19 -1
- package/evals/run.sh +32 -0
- package/evals/static/test_decisions.sh +150 -0
- package/evals/static/test_flowdef_codeowners_coverage.sh +62 -0
- package/evals/static/test_knowledge_providers.sh +23 -0
- package/evals/static/test_package.sh +19 -10
- package/evals/static/test_universal_bundles.sh +48 -3
- package/evals/static/test_workflow_skills.sh +41 -8
- package/kits/builder/flows/build.flow.json +3 -74
- package/kits/builder/flows/publish-learn.flow.json +90 -0
- package/kits/builder/kit.json +11 -0
- package/kits/builder/skills/builder-shape/SKILL.md +1 -1
- package/kits/builder/skills/deliver/SKILL.md +32 -11
- package/kits/builder/skills/design-probe/SKILL.md +4 -4
- package/kits/builder/skills/evidence-gate/SKILL.md +26 -1
- package/kits/builder/skills/execute-plan/SKILL.md +2 -2
- package/kits/builder/skills/fix-bug/SKILL.md +2 -0
- package/kits/builder/skills/gate-review/SKILL.md +6 -6
- package/kits/builder/skills/idea-to-backlog/SKILL.md +1 -1
- package/kits/builder/skills/learning-review/SKILL.md +7 -7
- package/kits/builder/skills/pickup-probe/SKILL.md +5 -4
- package/kits/builder/skills/plan-work/SKILL.md +27 -5
- package/kits/builder/skills/pull-work/SKILL.md +148 -5
- package/kits/builder/skills/release-readiness/SKILL.md +4 -4
- package/kits/builder/skills/review-work/SKILL.md +24 -1
- package/kits/builder/skills/tdd-workflow/SKILL.md +2 -0
- package/kits/builder/skills/verify-work/SKILL.md +8 -1
- package/kits/catalog.json +6 -0
- package/kits/knowledge/kit.json +35 -0
- package/kits/knowledge/providers/conformance/fixtures/git-repo/CONTEXT.md +12 -0
- package/kits/knowledge/providers/conformance/fixtures/git-repo/docs/decisions/old-sprocket-shape.md +13 -0
- package/kits/knowledge/providers/conformance/fixtures/git-repo/docs/decisions/sprocket-shape.md +14 -0
- package/kits/knowledge/providers/conformance/fixtures/git-repo/docs/decisions/widget-format.md +14 -0
- package/kits/knowledge/providers/conformance/fixtures/git-repo/docs/learnings/fixture-learning.md +7 -0
- package/kits/knowledge/providers/conformance/fixtures/work-item/issues.json +30 -0
- package/kits/knowledge/providers/conformance/suite.test.js +125 -0
- package/kits/knowledge/providers/git-repo/index.js +236 -0
- package/kits/knowledge/providers/health/health-pass.test.js +99 -0
- package/kits/knowledge/providers/health/index.js +153 -0
- package/kits/knowledge/providers/index.js +24 -0
- package/kits/knowledge/providers/lib/model.js +91 -0
- package/kits/knowledge/providers/lib/schema-validate.js +119 -0
- package/kits/knowledge/providers/markdown-vault/index.js +169 -0
- package/kits/knowledge/providers/work-item/index.js +204 -0
- package/kits/veritas-governance/adapter/readiness-to-trust-bundle.mjs +178 -0
- package/kits/veritas-governance/docs/README.md +75 -0
- package/kits/veritas-governance/fixtures/readiness/not-ready.readiness-report.json +4645 -0
- package/kits/veritas-governance/fixtures/readiness/ready.readiness-report.json +4403 -0
- package/kits/veritas-governance/flows/readiness-check.flow.json +35 -0
- package/kits/veritas-governance/kit.json +16 -0
- package/package.json +11 -5
- package/packaging/README.md +2 -2
- package/packaging/conformance/fixtures/config-protection--allow-read-state-json.json +20 -0
- package/packaging/conformance/fixtures/config-protection--block-write-state-json.json +20 -0
- package/packaging/conformance/fixtures/stop-goal-fit--block-bundle-disputed-claim.json +1 -1
- package/packaging/conformance/fixtures/stop-goal-fit--block-capture-contradicts-claimed-pass.json +1 -1
- package/packaging/manifest.json +9 -10
- package/schemas/assignment-provider-settings.schema.json +125 -0
- package/schemas/decision-record.schema.json +109 -0
- package/schemas/knowledge/edge.schema.json +54 -0
- package/schemas/knowledge/health-report.schema.json +45 -0
- package/schemas/knowledge/node.schema.json +49 -0
- package/schemas/knowledge/proposal.schema.json +53 -0
- package/schemas/workflow-evidence.schema.json +1 -1
- package/schemas/workflow-state.schema.json +5 -0
- package/scripts/README.md +8 -3
- package/scripts/check-content-boundary.cjs +1 -1
- package/scripts/check-decisions.cjs +356 -0
- package/scripts/ci/derive-claim-status.mjs +74 -0
- package/scripts/ci/trust-reconcile.js +957 -103
- package/scripts/git-status.sh +2 -2
- package/scripts/hooks/claude-telemetry-hook.js +37 -3
- package/scripts/hooks/codex-telemetry-hook.js +36 -2
- package/scripts/hooks/config-protection.js +102 -30
- package/scripts/hooks/evidence-capture.js +3 -3
- package/scripts/hooks/lib/actor-identity.js +368 -0
- package/scripts/hooks/lib/config-protection-remedies.js +69 -0
- package/scripts/hooks/lib/liveness-heartbeat.js +361 -0
- package/scripts/hooks/lib/liveness-policy.js +127 -0
- package/scripts/hooks/lib/liveness-read.js +105 -12
- package/scripts/hooks/lib/liveness-write.js +46 -0
- package/scripts/hooks/lib/local-artifact-paths.js +12 -6
- package/scripts/hooks/opencode-telemetry-hook.js +27 -1
- package/scripts/hooks/pi-telemetry-hook.js +27 -1
- package/scripts/hooks/stop-goal-fit.js +157 -26
- package/scripts/hooks/workflow-steering.js +77 -3
- package/scripts/install-codex-home.sh +138 -38
- package/scripts/statusline/flow-agents-statusline.js +2 -2
- package/src/cli/assignment-provider.ts +845 -0
- package/src/cli/console-learning-projection.ts +3 -2
- package/src/cli/effective-assignment-provider-settings.ts +112 -0
- package/src/cli/init.ts +101 -21
- package/src/cli/kit.ts +37 -6
- package/src/cli/public-api.test.mjs +58 -6
- package/src/cli/pull-work-provider.ts +1 -1
- package/src/cli/trust-bundle-policy-order.test.mjs +87 -0
- package/src/cli/usage-feedback.ts +3 -3
- package/src/cli/validate-hook-influence.ts +1 -0
- package/src/cli/validate-workflow-artifacts.ts +27 -7
- package/src/cli/veritas-readiness-adapter.test.mjs +267 -0
- package/src/cli/workflow-artifact-cleanup-audit.ts +35 -2
- package/src/cli/workflow-sidecar.ts +933 -168
- package/src/cli.ts +6 -0
- package/src/flow-kit/validate.ts +74 -1
- package/src/index.ts +6 -2
- package/src/lib/flow-resolver.ts +123 -55
- package/src/lib/local-artifact-root.ts +24 -5
- package/src/lib/workflow-learning-projection.ts +2 -2
- package/src/runtime-adapters.ts +61 -15
- package/src/tools/build-universal-bundles.ts +49 -16
- package/src/tools/generate-context-map.ts +10 -8
- package/src/tools/validate-source-tree.ts +24 -1
|
@@ -2,6 +2,7 @@ import * as fs from "node:fs";
|
|
|
2
2
|
import { fileURLToPath } from "node:url";
|
|
3
3
|
import * as path from "node:path";
|
|
4
4
|
import { flagBool, flagString, parseArgs } from "../lib/args.js";
|
|
5
|
+
import { defaultArtifactRootForRead } from "../lib/local-artifact-root.js";
|
|
5
6
|
import { buildWorkflowLearningProjection, readWorkflowLearningSources } from "../lib/workflow-learning-projection.js";
|
|
6
7
|
const SAFE_SEGMENT = /^[A-Za-z0-9._-]+$/;
|
|
7
8
|
function printHelp() {
|
|
@@ -10,7 +11,7 @@ function printHelp() {
|
|
|
10
11
|
console.log("Build an inert Console learning projection from local workflow learning sidecars.");
|
|
11
12
|
console.log("");
|
|
12
13
|
console.log("Options:");
|
|
13
|
-
console.log(" --artifact-root <path> Workflow artifact root to scan (default: .flow-agents)");
|
|
14
|
+
console.log(" --artifact-root <path> Workflow artifact root to scan (default: .kontourai/flow-agents)");
|
|
14
15
|
console.log(" --kontour-root <path> Local Kontour root to write under (default: .kontour)");
|
|
15
16
|
console.log(" --scope <id> Projection scope id (default: current directory name)");
|
|
16
17
|
console.log(" --scope-kind <kind> Projection scope kind (default: repo)");
|
|
@@ -84,7 +85,7 @@ export function main(argv = process.argv.slice(2)) {
|
|
|
84
85
|
return 0;
|
|
85
86
|
}
|
|
86
87
|
try {
|
|
87
|
-
const artifactRoot = path.resolve(flagString(flags, "artifact-root"
|
|
88
|
+
const artifactRoot = path.resolve(flagString(flags, "artifact-root") ?? defaultArtifactRootForRead());
|
|
88
89
|
const kontourRoot = path.resolve(flagString(flags, "kontour-root", ".kontour") ?? ".kontour");
|
|
89
90
|
const producer = requireSafeSegment(flagString(flags, "producer", "flow-agents-learning") ?? "flow-agents-learning", "--producer");
|
|
90
91
|
const scope = {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function main(argv?: string[]): number;
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import * as child_process from "node:child_process";
|
|
2
|
+
import * as fs from "node:fs";
|
|
3
|
+
import * as os from "node:os";
|
|
4
|
+
import * as path from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { parseArgs, flagString, flagBool } from "../lib/args.js";
|
|
7
|
+
import { readJson } from "../lib/fs.js";
|
|
8
|
+
// Mirrors src/cli/effective-backlog-settings.ts's loadSettings/currentRepo/merge/findProject/
|
|
9
|
+
// effective structure and its ask_user/configured result envelope exactly, pointed at the
|
|
10
|
+
// AssignmentProvider settings schema/example instead of the backlog equivalents (#290 Wave 2
|
|
11
|
+
// Task B). Deliberately NOT extracted into a shared helper with effective-backlog-settings.ts —
|
|
12
|
+
// see the #290 plan artifact's Unresolved Questions #3 for the recorded rationale (duplicating
|
|
13
|
+
// this file's small merge/repo-detection logic is lower-risk than refactoring a file outside
|
|
14
|
+
// this issue's declared scope).
|
|
15
|
+
const PROJECT_SETTINGS_RELATIVE_PATH = path.join("context", "settings", "assignment-provider-settings.json");
|
|
16
|
+
function loadSettings(file) {
|
|
17
|
+
if (!fs.existsSync(file))
|
|
18
|
+
return null;
|
|
19
|
+
const data = readJson(file);
|
|
20
|
+
if (data.schema_version !== "1.0")
|
|
21
|
+
throw new Error(`${file}: unsupported schema_version ${String(data.schema_version)}`);
|
|
22
|
+
return data;
|
|
23
|
+
}
|
|
24
|
+
function repoFromText(text) {
|
|
25
|
+
const match = text.trim().match(/github\.com[:/]([^/]+)\/([^/.]+)(?:\.git)?$/);
|
|
26
|
+
return match ? { owner: match[1], name: match[2] } : null;
|
|
27
|
+
}
|
|
28
|
+
function currentRepo(repoPath) {
|
|
29
|
+
try {
|
|
30
|
+
const out = child_process.execFileSync("git", ["-C", repoPath, "remote", "get-url", "origin"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
|
|
31
|
+
const repo = repoFromText(out);
|
|
32
|
+
if (repo)
|
|
33
|
+
return repo;
|
|
34
|
+
}
|
|
35
|
+
catch { }
|
|
36
|
+
const packagePath = path.join(repoPath, "package.json");
|
|
37
|
+
if (fs.existsSync(packagePath)) {
|
|
38
|
+
const data = readJson(packagePath);
|
|
39
|
+
const repository = data.repository;
|
|
40
|
+
const url = typeof repository === "object" && repository !== null ? repository.url : repository;
|
|
41
|
+
if (typeof url === "string")
|
|
42
|
+
return repoFromText(url);
|
|
43
|
+
}
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
function merge(base, override) {
|
|
47
|
+
if (!base && !override)
|
|
48
|
+
return null;
|
|
49
|
+
if (!base)
|
|
50
|
+
return structuredClone(override);
|
|
51
|
+
if (!override)
|
|
52
|
+
return structuredClone(base);
|
|
53
|
+
const out = structuredClone(base);
|
|
54
|
+
for (const [key, value] of Object.entries(override)) {
|
|
55
|
+
out[key] = typeof value === "object" && value !== null && typeof out[key] === "object" && out[key] !== null && !Array.isArray(value)
|
|
56
|
+
? merge(out[key], value)
|
|
57
|
+
: structuredClone(value);
|
|
58
|
+
}
|
|
59
|
+
return out;
|
|
60
|
+
}
|
|
61
|
+
function findProject(settings, repo) {
|
|
62
|
+
const projects = settings?.projects;
|
|
63
|
+
if (!Array.isArray(projects))
|
|
64
|
+
return null;
|
|
65
|
+
return projects.find((project) => {
|
|
66
|
+
const projectRepo = (project.project?.repo ?? {});
|
|
67
|
+
return projectRepo.owner === repo.owner && projectRepo.name === repo.name;
|
|
68
|
+
}) ?? null;
|
|
69
|
+
}
|
|
70
|
+
function defaultProjectSettingsPath() {
|
|
71
|
+
let cursor = path.dirname(fileURLToPath(import.meta.url));
|
|
72
|
+
while (true) {
|
|
73
|
+
const candidate = path.join(cursor, PROJECT_SETTINGS_RELATIVE_PATH);
|
|
74
|
+
if (fs.existsSync(candidate))
|
|
75
|
+
return candidate;
|
|
76
|
+
const parent = path.dirname(cursor);
|
|
77
|
+
if (parent === cursor)
|
|
78
|
+
return path.resolve(PROJECT_SETTINGS_RELATIVE_PATH);
|
|
79
|
+
cursor = parent;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
function effective(repoPath, projectSettings, globalSettings) {
|
|
83
|
+
const repo = currentRepo(repoPath);
|
|
84
|
+
const projectDoc = loadSettings(projectSettings);
|
|
85
|
+
const globalDoc = loadSettings(globalSettings);
|
|
86
|
+
if (!repo)
|
|
87
|
+
return [{ status: "ask_user", reason: "could_not_identify_current_repo", message: "Ask the user which assignment AssignmentProvider to use for this workspace.", resolution: { project_settings_path: projectSettings, global_settings_path: globalSettings } }, 2];
|
|
88
|
+
const effectiveSettings = merge(merge(merge(globalDoc?.defaults, findProject(globalDoc, repo)), projectDoc?.defaults), findProject(projectDoc, repo));
|
|
89
|
+
if (!effectiveSettings)
|
|
90
|
+
return [{ status: "ask_user", reason: "no_assignment_provider_settings", message: "Ask the user which assignment AssignmentProvider to use before claiming work.", current_repo: repo, resolution: { project_settings_path: projectSettings, global_settings_path: globalSettings, checked: ["project", "global"] } }, 2];
|
|
91
|
+
return [{ status: "configured", current_repo: repo, source: findProject(projectDoc, repo) || projectDoc?.defaults ? "project" : "global", precedence: ["project.projects match", "project.defaults", "global.projects match", "global.defaults"], settings: effectiveSettings }, 0];
|
|
92
|
+
}
|
|
93
|
+
export function main(argv = process.argv.slice(2)) {
|
|
94
|
+
const args = parseArgs(argv);
|
|
95
|
+
try {
|
|
96
|
+
const [result, code] = effective(path.resolve(flagString(args.flags, "repo-path", ".") ?? "."), path.resolve(flagString(args.flags, "project-settings", defaultProjectSettingsPath()) ?? ""), path.resolve(flagString(args.flags, "global-settings", path.join(os.homedir(), ".config", "flow-agents", "assignment-provider-settings.json")) ?? ""));
|
|
97
|
+
if (flagBool(args.flags, "json"))
|
|
98
|
+
console.log(JSON.stringify(result, null, 2));
|
|
99
|
+
else
|
|
100
|
+
console.log(`status: ${String(result.status)}`);
|
|
101
|
+
return code;
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
console.error(`error: ${error.message}`);
|
|
105
|
+
return 1;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
// Use process.exitCode (not process.exit) to allow stdout to be flushed before exit.
|
|
109
|
+
// Resolve real paths to handle symlinks (e.g. /tmp -> /private/tmp on macOS) so the
|
|
110
|
+
// entry-point guard fires correctly when the module is loaded directly as a script.
|
|
111
|
+
const _selfRealPath = (() => { try {
|
|
112
|
+
return fs.realpathSync(fileURLToPath(import.meta.url));
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
return fileURLToPath(import.meta.url);
|
|
116
|
+
} })();
|
|
117
|
+
const _argv1RealPath = (() => { try {
|
|
118
|
+
return fs.realpathSync(process.argv[1]);
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
return process.argv[1];
|
|
122
|
+
} })();
|
|
123
|
+
if (_selfRealPath === _argv1RealPath) {
|
|
124
|
+
process.exitCode = main();
|
|
125
|
+
}
|
package/build/src/cli/init.js
CHANGED
|
@@ -10,6 +10,7 @@ import { parseArgs, flagBool, flagList, flagString } from "../lib/args.js";
|
|
|
10
10
|
import { activateCodexLocal } from "../runtime-adapters.js";
|
|
11
11
|
import { main as buildBundles } from "../tools/build-universal-bundles.js";
|
|
12
12
|
import { root } from "../tools/common.js";
|
|
13
|
+
import { defaultCodexHome, durableInstallRecordPath } from "../lib/local-artifact-root.js";
|
|
13
14
|
const runtimeBundles = {
|
|
14
15
|
base: "base",
|
|
15
16
|
codex: "codex",
|
|
@@ -82,8 +83,8 @@ Options:
|
|
|
82
83
|
Honors FLOW_AGENTS_USER_CLAUDE_SETTINGS for test isolation.
|
|
83
84
|
opencode: merges opencode.json into ~/.config/opencode/opencode.json
|
|
84
85
|
(honors XDG_CONFIG_HOME; test isolation via FLOW_AGENTS_USER_OPENCODE_CONFIG).
|
|
85
|
-
codex: runs install-codex-home.sh into ~/.
|
|
86
|
-
(
|
|
86
|
+
codex: runs install-codex-home.sh into CODEX_HOME or ~/.codex
|
|
87
|
+
(hooks merged, not overwritten).
|
|
87
88
|
pi: NOT_VERIFIED (no documented global dir); warns and falls back to workspace default.
|
|
88
89
|
--telemetry-sink local-files|local-kontour-console|kontour-hosted-console|user-hosted-console
|
|
89
90
|
--console-url URL
|
|
@@ -174,9 +175,8 @@ function globalDest(runtime) {
|
|
|
174
175
|
return path.join(process.env["XDG_CONFIG_HOME"] ?? path.join(os.homedir(), ".config"), "opencode");
|
|
175
176
|
}
|
|
176
177
|
if (runtime === "codex") {
|
|
177
|
-
// codex --global routes to the
|
|
178
|
-
|
|
179
|
-
return path.join(os.homedir(), ".flow-agents", "codex");
|
|
178
|
+
// codex --global routes to the standard Codex home. --dest remains an explicit override.
|
|
179
|
+
return defaultCodexHome();
|
|
180
180
|
}
|
|
181
181
|
if (runtime === "pi") {
|
|
182
182
|
// pi has no documented global config dir.
|
|
@@ -268,6 +268,75 @@ function ensureBundle(runtime) {
|
|
|
268
268
|
throw new Error(`bundle installer missing: ${bundle}`);
|
|
269
269
|
return bundle;
|
|
270
270
|
}
|
|
271
|
+
// The bundle's hook commands resolve the flow-agents scripts directory via
|
|
272
|
+
// ${CLAUDE_PROJECT_DIR:-$(pwd)}. That is correct for a project-scoped install
|
|
273
|
+
// (installBundle rsyncs scripts/ alongside .claude/, so CLAUDE_PROJECT_DIR ==
|
|
274
|
+
// the install destination). It is NOT correct for a --global install: there
|
|
275
|
+
// is no per-destination copy of scripts/, and CLAUDE_PROJECT_DIR varies with
|
|
276
|
+
// whichever project happens to be open, so the hook resolves to a path that
|
|
277
|
+
// exists in at most one project (and never for most sessions). Global
|
|
278
|
+
// installs need an absolute, session-independent path instead.
|
|
279
|
+
const GLOBAL_INSTALL_PROJECT_DIR_PREFIX = /root="\$\{CLAUDE_PROJECT_DIR:-\$\(pwd\)\}";\s*/g;
|
|
280
|
+
const GLOBAL_INSTALL_PROJECT_DIR_VAR = /"\$root\//g;
|
|
281
|
+
function rewriteCommandForGlobalInstall(command, sourceRoot) {
|
|
282
|
+
return command
|
|
283
|
+
.replace(GLOBAL_INSTALL_PROJECT_DIR_PREFIX, "")
|
|
284
|
+
.replace(GLOBAL_INSTALL_PROJECT_DIR_VAR, `"${sourceRoot}/`);
|
|
285
|
+
}
|
|
286
|
+
/** Recursively rewrite every `command` string found under `value` in place. */
|
|
287
|
+
function rewriteCommandsForGlobalInstall(value, sourceRoot) {
|
|
288
|
+
if (Array.isArray(value)) {
|
|
289
|
+
for (const item of value)
|
|
290
|
+
rewriteCommandsForGlobalInstall(item, sourceRoot);
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
if (!value || typeof value !== "object")
|
|
294
|
+
return;
|
|
295
|
+
const obj = value;
|
|
296
|
+
for (const key of Object.keys(obj)) {
|
|
297
|
+
if (key === "command" && typeof obj[key] === "string") {
|
|
298
|
+
obj[key] = rewriteCommandForGlobalInstall(obj[key], sourceRoot);
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
301
|
+
rewriteCommandsForGlobalInstall(obj[key], sourceRoot);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Additively copy every file under srcDir into destDir, creating directories
|
|
306
|
+
* as needed and overwriting files whose content changed. Never deletes files
|
|
307
|
+
* in destDir that srcDir does not own — destDir may contain unrelated content
|
|
308
|
+
* (other kits, other tools) that this sync must not touch.
|
|
309
|
+
*/
|
|
310
|
+
function copyDirMerge(srcDir, destDir) {
|
|
311
|
+
let added = 0;
|
|
312
|
+
let updated = 0;
|
|
313
|
+
if (!fs.existsSync(srcDir))
|
|
314
|
+
return { added, updated };
|
|
315
|
+
for (const entry of fs.readdirSync(srcDir, { withFileTypes: true })) {
|
|
316
|
+
const srcPath = path.join(srcDir, entry.name);
|
|
317
|
+
const destPath = path.join(destDir, entry.name);
|
|
318
|
+
if (entry.isDirectory()) {
|
|
319
|
+
const nested = copyDirMerge(srcPath, destPath);
|
|
320
|
+
added += nested.added;
|
|
321
|
+
updated += nested.updated;
|
|
322
|
+
continue;
|
|
323
|
+
}
|
|
324
|
+
if (!entry.isFile())
|
|
325
|
+
continue;
|
|
326
|
+
const content = fs.readFileSync(srcPath);
|
|
327
|
+
if (fs.existsSync(destPath)) {
|
|
328
|
+
if (Buffer.compare(fs.readFileSync(destPath), content) === 0)
|
|
329
|
+
continue;
|
|
330
|
+
updated += 1;
|
|
331
|
+
}
|
|
332
|
+
else {
|
|
333
|
+
fs.mkdirSync(path.dirname(destPath), { recursive: true });
|
|
334
|
+
added += 1;
|
|
335
|
+
}
|
|
336
|
+
fs.writeFileSync(destPath, content);
|
|
337
|
+
}
|
|
338
|
+
return { added, updated };
|
|
339
|
+
}
|
|
271
340
|
function installBundle(bundle, options) {
|
|
272
341
|
const args = ["install.sh", options.dest];
|
|
273
342
|
for (const sink of options.telemetrySinks)
|
|
@@ -334,10 +403,13 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
334
403
|
if (!options.global)
|
|
335
404
|
checkScopeCollision();
|
|
336
405
|
}
|
|
337
|
-
// --global for claude-code: merge
|
|
338
|
-
//
|
|
339
|
-
// not
|
|
340
|
-
//
|
|
406
|
+
// --global for claude-code: merge hook-wiring into the global/user-level
|
|
407
|
+
// settings dir, plus additively sync skills/agents so a global install
|
|
408
|
+
// does not silently go stale as new Builder Kit skills ship. This does
|
|
409
|
+
// NOT rsync the full workspace bundle (no context/, powers/, prompts/,
|
|
410
|
+
// etc.) — only the parts a global Claude Code user-config needs. The
|
|
411
|
+
// global settings dir is the claude config root, so the settings.json
|
|
412
|
+
// lives directly in dest (not dest/.claude/).
|
|
341
413
|
if (options.global && options.runtime === "claude-code") {
|
|
342
414
|
const bundle = ensureBundle(options.runtime);
|
|
343
415
|
// For --global, dest is ~/.claude/ (the global settings dir).
|
|
@@ -353,6 +425,10 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
353
425
|
// Remove permissive defaults (not appropriate for global user settings).
|
|
354
426
|
delete managed["permissions"];
|
|
355
427
|
delete managed["skipDangerousModePermissionPrompt"];
|
|
428
|
+
// See rewriteCommandsForGlobalInstall: bundle hook commands assume
|
|
429
|
+
// CLAUDE_PROJECT_DIR points at this package; a global install must not
|
|
430
|
+
// depend on which project is currently open, so pin to an absolute path.
|
|
431
|
+
rewriteCommandsForGlobalInstall(managed, root);
|
|
356
432
|
fs.mkdirSync(options.dest, { recursive: true });
|
|
357
433
|
const destSettingsPath = path.join(options.dest, "settings.json");
|
|
358
434
|
const installMergePath = path.join(root, "scripts", "install-merge.js");
|
|
@@ -371,16 +447,21 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
371
447
|
const tmp = `${destSettingsPath}.tmp.${process.pid}`;
|
|
372
448
|
fs.writeFileSync(tmp, `${JSON.stringify(merged, null, 2)}\n`, "utf8");
|
|
373
449
|
fs.renameSync(tmp, destSettingsPath);
|
|
450
|
+
// Additive skills/agents sync: adds new files and updates changed ones,
|
|
451
|
+
// never deletes — dest may hold unrelated content from other kits/tools.
|
|
452
|
+
const skillsSync = copyDirMerge(path.join(bundle, ".claude", "skills"), path.join(options.dest, "skills"));
|
|
453
|
+
const agentsSync = copyDirMerge(path.join(bundle, ".claude", "agents"), path.join(options.dest, "agents"));
|
|
374
454
|
// Write version stamp.
|
|
375
|
-
const
|
|
455
|
+
const recordPath = durableInstallRecordPath(options.dest);
|
|
456
|
+
const installRecordDir = path.dirname(recordPath);
|
|
376
457
|
fs.mkdirSync(installRecordDir, { recursive: true });
|
|
377
458
|
const pkgJson = JSON.parse(fs.readFileSync(path.join(root, "package.json"), "utf8"));
|
|
378
459
|
const record = { version: pkgJson["version"] ?? "0.0.0", installedAt: new Date().toISOString(), runtime: "claude-code", global: true };
|
|
379
|
-
const recordPath = path.join(installRecordDir, "install.json");
|
|
380
460
|
const recordTmp = `${recordPath}.tmp.${process.pid}`;
|
|
381
461
|
fs.writeFileSync(recordTmp, `${JSON.stringify(record, null, 2)}\n`, "utf8");
|
|
382
462
|
fs.renameSync(recordTmp, recordPath);
|
|
383
463
|
console.log(`Flow Agents global hooks merged for claude-code in ${options.dest}`);
|
|
464
|
+
console.log(`Synced skills (+${skillsSync.added} new, ~${skillsSync.updated} updated) and agents (+${agentsSync.added} new, ~${agentsSync.updated} updated) in ${options.dest}`);
|
|
384
465
|
return 0;
|
|
385
466
|
}
|
|
386
467
|
// --global for opencode: merge FA opencode.json into the global opencode config dir.
|
|
@@ -415,11 +496,11 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
415
496
|
`, "utf8");
|
|
416
497
|
fs.renameSync(tmp, destConfigPath);
|
|
417
498
|
// Write version stamp.
|
|
418
|
-
const
|
|
499
|
+
const recordPath = durableInstallRecordPath(options.dest);
|
|
500
|
+
const installRecordDir = path.dirname(recordPath);
|
|
419
501
|
fs.mkdirSync(installRecordDir, { recursive: true });
|
|
420
502
|
const pkgJson = JSON.parse(fs.readFileSync(path.join(root, "package.json"), "utf8"));
|
|
421
503
|
const record = { version: pkgJson["version"] ?? "0.0.0", installedAt: new Date().toISOString(), runtime: "opencode", global: true };
|
|
422
|
-
const recordPath = path.join(installRecordDir, "install.json");
|
|
423
504
|
const recordTmp = `${recordPath}.tmp.${process.pid}`;
|
|
424
505
|
fs.writeFileSync(recordTmp, `${JSON.stringify(record, null, 2)}
|
|
425
506
|
`, "utf8");
|
|
@@ -427,9 +508,8 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
427
508
|
console.log(`Flow Agents global config merged for opencode in ${options.dest}`);
|
|
428
509
|
return 0;
|
|
429
510
|
}
|
|
430
|
-
// --global for codex: run install-codex-home.sh to install into the
|
|
431
|
-
//
|
|
432
|
-
// Pass through telemetry/console args and honor --dest override for sandbox testing.
|
|
511
|
+
// --global for codex: run install-codex-home.sh to install into the Codex home.
|
|
512
|
+
// Defaults to CODEX_HOME or ~/.codex. --dest remains an explicit override.
|
|
433
513
|
if (options.global && options.runtime === "codex") {
|
|
434
514
|
const codexHomeScript = path.join(root, "scripts", "install-codex-home.sh");
|
|
435
515
|
if (!fs.existsSync(codexHomeScript)) {
|
|
@@ -452,7 +532,10 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
452
532
|
console.error(`flow-agents init: unable to run install-codex-home.sh: ${result.error.message}`);
|
|
453
533
|
return 1;
|
|
454
534
|
}
|
|
455
|
-
|
|
535
|
+
const installed = result.status ?? 1;
|
|
536
|
+
if (installed !== 0)
|
|
537
|
+
return installed;
|
|
538
|
+
return activateKits(options);
|
|
456
539
|
}
|
|
457
540
|
// --global for pi: NOT_VERIFIED (no documented global dir). Warn and fall through to workspace install.
|
|
458
541
|
if (options.global && options.runtime === "pi") {
|
|
@@ -541,11 +624,11 @@ function dogfoodClaudeCode(bundleRoot, dest) {
|
|
|
541
624
|
fs.writeFileSync(tmp, `${JSON.stringify(merged, null, 2)}\n`, "utf8");
|
|
542
625
|
fs.renameSync(tmp, destSettingsPath);
|
|
543
626
|
// Write version stamp.
|
|
544
|
-
const
|
|
627
|
+
const recordPath = durableInstallRecordPath(dest);
|
|
628
|
+
const installRecordDir = path.dirname(recordPath);
|
|
545
629
|
fs.mkdirSync(installRecordDir, { recursive: true });
|
|
546
630
|
const pkgJson = JSON.parse(fs.readFileSync(path.join(root, "package.json"), "utf8"));
|
|
547
631
|
const record = { version: pkgJson["version"] ?? "0.0.0", installedAt: new Date().toISOString(), runtime: "claude-code" };
|
|
548
|
-
const recordPath = path.join(installRecordDir, "install.json");
|
|
549
632
|
const recordTmp = `${recordPath}.tmp.${process.pid}`;
|
|
550
633
|
fs.writeFileSync(recordTmp, `${JSON.stringify(record, null, 2)}\n`, "utf8");
|
|
551
634
|
fs.renameSync(recordTmp, recordPath);
|
|
@@ -584,11 +667,11 @@ function dogfoodCodex(bundleRoot, dest) {
|
|
|
584
667
|
fs.writeFileSync(tmp, `${JSON.stringify(merged, null, 2)}\n`, "utf8");
|
|
585
668
|
fs.renameSync(tmp, destHooksPath);
|
|
586
669
|
// Write version stamp.
|
|
587
|
-
const
|
|
670
|
+
const recordPath = durableInstallRecordPath(dest);
|
|
671
|
+
const installRecordDir = path.dirname(recordPath);
|
|
588
672
|
fs.mkdirSync(installRecordDir, { recursive: true });
|
|
589
673
|
const pkgJson = JSON.parse(fs.readFileSync(path.join(root, "package.json"), "utf8"));
|
|
590
674
|
const record = { version: pkgJson["version"] ?? "0.0.0", installedAt: new Date().toISOString(), runtime: "codex" };
|
|
591
|
-
const recordPath = path.join(installRecordDir, "install.json");
|
|
592
675
|
const recordTmp = `${recordPath}.tmp.${process.pid}`;
|
|
593
676
|
fs.writeFileSync(recordTmp, `${JSON.stringify(record, null, 2)}\n`, "utf8");
|
|
594
677
|
fs.renameSync(recordTmp, recordPath);
|
package/build/src/cli/kit.js
CHANGED
|
@@ -6,12 +6,17 @@ import * as path from "node:path";
|
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
7
|
import { parseArgs, flagBool, flagString } from "../lib/args.js";
|
|
8
8
|
import { assertPathContained, copyDir, isoNow, readJson, walkFiles, writeJson } from "../lib/fs.js";
|
|
9
|
-
import { assertKitRepository, deriveKitTargets } from "../flow-kit/validate.js";
|
|
9
|
+
import { assertKitRepository, deriveKitTargets, parseKitDependencies } from "../flow-kit/validate.js";
|
|
10
10
|
import { activateCodexLocal, activateStrandsLocal } from "../runtime-adapters.js";
|
|
11
|
+
import { defaultCodexHome } from "../lib/local-artifact-root.js";
|
|
11
12
|
const REGISTRY_REL = path.join("kits", "local", "installed-kits.json");
|
|
12
13
|
const REPOSITORIES_REL = path.join("kits", "local", "repositories");
|
|
13
14
|
function registryPath(dest) { return path.join(dest, REGISTRY_REL); }
|
|
14
15
|
function installedPath(dest, kitId) { return path.join(dest, REPOSITORIES_REL, kitId); }
|
|
16
|
+
function resolveDest(flags) {
|
|
17
|
+
const explicit = flagString(flags, "dest");
|
|
18
|
+
return path.resolve(explicit ?? defaultCodexHome());
|
|
19
|
+
}
|
|
15
20
|
function loadRegistry(dest) {
|
|
16
21
|
const file = registryPath(dest);
|
|
17
22
|
if (!fs.existsSync(file))
|
|
@@ -19,6 +24,28 @@ function loadRegistry(dest) {
|
|
|
19
24
|
const data = readJson(file);
|
|
20
25
|
return { schema_version: data.schema_version ?? "1.0", kits: Array.isArray(data.kits) ? data.kits.filter((item) => typeof item === "object" && item !== null) : [] };
|
|
21
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* Emit a non-blocking warning for each declared kit dependency that is not present
|
|
29
|
+
* in the destination's LOCAL registry.
|
|
30
|
+
*
|
|
31
|
+
* Scope limitation (v1, accepted): this check only sees the local installed-kits
|
|
32
|
+
* registry at `dest`, NOT the built-in Kit Catalog. A dependency satisfied solely by
|
|
33
|
+
* a built-in catalog kit will still warn here. Presence is enforced authoritatively
|
|
34
|
+
* (hard error) at activation time against the full discovered inventory
|
|
35
|
+
* (built-in catalog + local registry) — see src/runtime-adapters.ts.
|
|
36
|
+
*/
|
|
37
|
+
function warnUninstalledDependencies(manifest, manifestPath, dest) {
|
|
38
|
+
const { entries } = parseKitDependencies(manifest, manifestPath);
|
|
39
|
+
if (!entries.length)
|
|
40
|
+
return;
|
|
41
|
+
const installed = new Set(loadRegistry(dest).kits.map((entry) => String(entry.id ?? "")));
|
|
42
|
+
const kitId = String(manifest.id);
|
|
43
|
+
for (const dep of entries) {
|
|
44
|
+
if (!installed.has(dep.kit_id)) {
|
|
45
|
+
console.log(`warning: kit '${kitId}' declares a dependency on '${dep.kit_id}'${dep.reason ? ` (${dep.reason})` : ""} which is not installed at ${dest}; install it first with 'flow-agents kit install <source>' or activation will fail`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
22
49
|
function contentHash(root) {
|
|
23
50
|
const hash = crypto.createHash("sha256");
|
|
24
51
|
for (const file of walkFiles(root)) {
|
|
@@ -73,7 +100,7 @@ async function install(argv) {
|
|
|
73
100
|
}
|
|
74
101
|
async function installLocalSource(source, argv) {
|
|
75
102
|
const args = parseArgs(argv);
|
|
76
|
-
const dest =
|
|
103
|
+
const dest = resolveDest(args.flags);
|
|
77
104
|
let manifest;
|
|
78
105
|
try {
|
|
79
106
|
manifest = await assertKitRepository(source);
|
|
@@ -85,10 +112,12 @@ async function installLocalSource(source, argv) {
|
|
|
85
112
|
return 1;
|
|
86
113
|
}
|
|
87
114
|
const kitId = String(manifest.id);
|
|
115
|
+
warnUninstalledDependencies(manifest, path.join(source, "kit.json"), dest);
|
|
88
116
|
const hash = contentHash(source);
|
|
89
117
|
const registry = loadRegistry(dest);
|
|
90
118
|
const existing = registry.kits.find((entry) => entry.id === kitId);
|
|
91
119
|
const target = installedPath(dest, kitId);
|
|
120
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
92
121
|
assertPathContained(dest, target);
|
|
93
122
|
const sourceText = source;
|
|
94
123
|
if (existing && existing.source !== sourceText && !flagBool(args.flags, "update")) {
|
|
@@ -127,7 +156,7 @@ async function installGitSource(rawUrl, argv) {
|
|
|
127
156
|
}
|
|
128
157
|
if (!ref)
|
|
129
158
|
ref = flagString(args.flags, "ref") ?? null;
|
|
130
|
-
const dest =
|
|
159
|
+
const dest = resolveDest(args.flags);
|
|
131
160
|
const force = flagBool(args.flags, "force") ?? false;
|
|
132
161
|
const update = flagBool(args.flags, "update") ?? false;
|
|
133
162
|
// Shallow-clone into a temporary directory.
|
|
@@ -161,10 +190,12 @@ async function installGitSource(rawUrl, argv) {
|
|
|
161
190
|
}
|
|
162
191
|
// Delegate to the shared install logic (copy + registry update).
|
|
163
192
|
const kitId = String(manifest.id);
|
|
193
|
+
warnUninstalledDependencies(manifest, path.join(tmpBase, "kit.json"), dest);
|
|
164
194
|
const hash = kitContentHash(tmpBase);
|
|
165
195
|
const registry = loadRegistry(dest);
|
|
166
196
|
const existing = registry.kits.find((entry) => entry.id === kitId);
|
|
167
197
|
const target = installedPath(dest, kitId);
|
|
198
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
168
199
|
assertPathContained(dest, target);
|
|
169
200
|
const sourceText = repoUrl + (ref ? `#${ref}` : "");
|
|
170
201
|
if (existing && existing.source !== sourceText && !update) {
|
|
@@ -197,7 +228,7 @@ async function installGitSource(rawUrl, argv) {
|
|
|
197
228
|
}
|
|
198
229
|
function list(argv) {
|
|
199
230
|
const args = parseArgs(argv);
|
|
200
|
-
const dest =
|
|
231
|
+
const dest = resolveDest(args.flags);
|
|
201
232
|
const entries = loadRegistry(dest).kits;
|
|
202
233
|
if (!entries.length) {
|
|
203
234
|
console.log("No local Flow Kits installed.");
|
|
@@ -210,7 +241,7 @@ function list(argv) {
|
|
|
210
241
|
}
|
|
211
242
|
function status(argv) {
|
|
212
243
|
const args = parseArgs(argv);
|
|
213
|
-
const dest =
|
|
244
|
+
const dest = resolveDest(args.flags);
|
|
214
245
|
let entries = loadRegistry(dest).kits;
|
|
215
246
|
const kitId = args.positionals[0];
|
|
216
247
|
if (kitId) {
|
|
@@ -233,7 +264,7 @@ function status(argv) {
|
|
|
233
264
|
const AVAILABLE_ADAPTERS = ["codex-local", "strands-local"];
|
|
234
265
|
function activate(argv) {
|
|
235
266
|
const args = parseArgs(argv);
|
|
236
|
-
const dest =
|
|
267
|
+
const dest = resolveDest(args.flags);
|
|
237
268
|
const sourceRoot = path.resolve(flagString(args.flags, "source-root", path.resolve(path.dirname(process.argv[1]), "..")) ?? ".");
|
|
238
269
|
const adapter = flagString(args.flags, "adapter");
|
|
239
270
|
if (adapter && !AVAILABLE_ADAPTERS.includes(adapter)) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import { fileURLToPath } from "node:url";
|
|
3
3
|
import { parseArgs, flagList, flagString } from "../lib/args.js";
|
|
4
|
-
const FLOW_ARTIFACT_PATTERN = /(?<path>\.flow-agents\/[^\s`'")]+)/g;
|
|
4
|
+
const FLOW_ARTIFACT_PATTERN = /(?<path>\.kontourai\/flow-agents\/[^\s`'")]+)/g;
|
|
5
5
|
const GITHUB_ISSUE_PATTERN = /(?:(?<owner>[A-Za-z0-9_.-]+)\/(?<repo>[A-Za-z0-9_.-]+))?#(?<number>\d+)/g;
|
|
6
6
|
const GITHUB_URL_PATTERN = /https:\/\/github\.com\/(?<owner>[^/\s]+)\/(?<repo>[^/\s]+)\/(?:issues|pull)\/(?<number>\d+)/g;
|
|
7
7
|
const BLOCKER_PATTERN = /\b(?:blocked by|depends on|waiting on|requires|should follow|coordinate with)\b[:\s-]*(?<refs>[^\n]+)/gi;
|
|
@@ -3,7 +3,7 @@ import { fileURLToPath } from "node:url";
|
|
|
3
3
|
import * as os from "node:os";
|
|
4
4
|
import * as path from "node:path";
|
|
5
5
|
import { parseArgs, flagBool, flagList, flagString } from "../lib/args.js";
|
|
6
|
-
import { defaultArtifactRootForRead, defaultTelemetryDirForRead, defaultTelemetryDirsForRead, telemetryDataDir, flowAgentsArtifactRoot
|
|
6
|
+
import { defaultArtifactRootForRead, defaultTelemetryDirForRead, defaultTelemetryDirsForRead, telemetryDataDir, flowAgentsArtifactRoot } from "../lib/local-artifact-root.js";
|
|
7
7
|
const VALID_RESULTS = new Set(["success", "partial", "failure", "not_verified"]);
|
|
8
8
|
function telemetryDir(flags) {
|
|
9
9
|
const explicit = flagString(flags, "telemetry-dir") ?? process.env.TELEMETRY_DATA_DIR;
|
|
@@ -152,7 +152,7 @@ function syncArtifacts(argv) {
|
|
|
152
152
|
const dir = telemetryDir(flags);
|
|
153
153
|
ensureSafeDir(dir);
|
|
154
154
|
const artifacts = flagList(flags, "artifact-dir");
|
|
155
|
-
const records = (artifacts.length ? artifacts : [flowAgentsArtifactRoot()
|
|
155
|
+
const records = (artifacts.length ? artifacts : [flowAgentsArtifactRoot()]).flatMap((item) => fs.existsSync(item) ? artifactOutcomes(item, flags) : []);
|
|
156
156
|
writeJsonlUpsert(path.join(dir, "outcomes.jsonl"), records, "outcome_id");
|
|
157
157
|
if (!flagBool(flags, "quiet"))
|
|
158
158
|
console.log(`synced ${records.length} artifact outcome(s) to ${path.join(dir, "outcomes.jsonl")}`);
|
|
@@ -382,7 +382,7 @@ function discoverProjects(root) {
|
|
|
382
382
|
if (!fs.existsSync(root))
|
|
383
383
|
return [];
|
|
384
384
|
const candidates = [root, ...fs.readdirSync(root).map((name) => path.join(root, name))];
|
|
385
|
-
return candidates.filter((candidate) => fs.existsSync(flowAgentsArtifactRoot(candidate))
|
|
385
|
+
return candidates.filter((candidate) => fs.existsSync(flowAgentsArtifactRoot(candidate))).map((repoRoot) => {
|
|
386
386
|
const name = path.basename(repoRoot);
|
|
387
387
|
return { name, repo: name, repo_root: repoRoot, artifact_dir: defaultArtifactRootForRead(repoRoot), input_telemetry_dir: defaultTelemetryDirForRead(repoRoot), runtime: "codex", skill_ids: [] };
|
|
388
388
|
});
|
|
@@ -7,6 +7,7 @@ const validEvents = new Set(["UserPromptSubmit", "PostToolUse", "Stop"]);
|
|
|
7
7
|
const requiredCases = {
|
|
8
8
|
"dev-builder-build-requires-pickup-probe-before-plan": { description: "missing pickup Probe before plan", tier: "design-target", must_include: ["design-probe", "accepted gaps", "provider_state", "conflict_risks"], agent_must_do: ["route decision_gap back to design-probe/pickup Probe"] },
|
|
9
9
|
"dev-builder-review-before-verify-after-execute": { description: "review-before-verify after execute", tier: "adapter", event: "PostToolUse", must_include: ["Next: review", "then verify", "report only"], agent_must_do: ["review-work for report-only critique before verify-work", "not count critique.json as verification evidence"] },
|
|
10
|
+
"dev-builder-route-fresh-coding-prompt": { description: "fresh coding prompt routes into Builder workflow", tier: "adapter", event: "UserPromptSubmit", must_include: ["BUILDER WORKFLOW ROUTE", "activate `deliver`", "--flow-id builder.build", "plan-work -> execute-plan -> review-work -> verify-work", "publish/release-readiness and learning-review"], agent_must_do: ["activate Builder Kit delivery workflow before source edits", "use deliver by default for coding/build work", "do not bypass plan-work -> execute-plan -> review-work -> verify-work", "publish, release readiness, and learning feedback"] },
|
|
10
11
|
"dev-verify-fail-preserves-trace-before-rework": { description: "verify failure route-back with preserved FAIL evidence", tier: "design-target", must_include: ["If verdict=FAIL: record the FAIL artifact"], agent_must_do: ["write the FAIL artifact before routing back", "route to plan"] },
|
|
11
12
|
"codex-claude-strict-stop-adapter-contract": { description: "Goal Fit stop adapter behavior", tier: "adapter", hook: "stop-goal-fit", event: "Stop", must_include: ["Goal Fit warning"], agent_must_do: ["treat strict Stop guidance as a blocker"] },
|
|
12
13
|
"codex-live-context-gap": { description: "Codex documented runtime gap", tier: "documented-runtime-gap", must_include: ["STATE:", "not_verified"], agent_must_do: ["record NOT_VERIFIED for live influence"] },
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import * as fs from "node:fs";
|
|
3
3
|
import * as path from "node:path";
|
|
4
|
-
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
// Resolve bundled JSON Schemas relative to this compiled script's own package
|
|
6
|
+
// location (build/src/cli/validate-workflow-artifacts.js -> ../../../schemas), NOT
|
|
7
|
+
// process.cwd(), so `flow-agents-validate-artifacts` works from any repo (the artifact
|
|
8
|
+
// paths to validate are still taken from argv/cwd). Mirrors the package-relative pattern
|
|
9
|
+
// used for liveness-read.js in workflow-sidecar.ts.
|
|
10
|
+
const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..", "..");
|
|
5
11
|
const statusRe = /\[(PASS|FAIL|NOT_VERIFIED|SKIP|PARTIAL)\]/i;
|
|
6
12
|
const dateTimeRe = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/;
|
|
7
13
|
const sidecarSchemas = {
|
|
@@ -311,11 +317,12 @@ function readJson(file) {
|
|
|
311
317
|
}
|
|
312
318
|
function validateSidecar(file) {
|
|
313
319
|
const { value, issues } = readJson(file);
|
|
320
|
+
const warnings = [];
|
|
314
321
|
if (value === undefined)
|
|
315
|
-
return issues;
|
|
322
|
+
return { issues, warnings };
|
|
316
323
|
const schemaFile = sidecarSchemas[path.basename(file)];
|
|
317
324
|
if (schemaFile) {
|
|
318
|
-
const schema = JSON.parse(readText(path.join(
|
|
325
|
+
const schema = JSON.parse(readText(path.join(packageRoot, schemaFile)));
|
|
319
326
|
validateSchemaValue(file, value, schema, path.basename(file), issues, schema);
|
|
320
327
|
}
|
|
321
328
|
if (path.basename(file) === "evidence.json") {
|
|
@@ -359,7 +366,10 @@ function validateSidecar(file) {
|
|
|
359
366
|
issues.push({ path: file, message: "deploy decision requires post_deploy_checks to be planned or pass" });
|
|
360
367
|
}
|
|
361
368
|
}
|
|
362
|
-
|
|
369
|
+
if (path.basename(file) === "state.json" && value && typeof value === "object" && !Array.isArray(value) && !("branch" in value)) {
|
|
370
|
+
warnings.push({ path: file, message: "state.json has no branch field (legacy/pre-#289 session) — re-run `ensure-session` to backfill agent/<actor>/<slug>." });
|
|
371
|
+
}
|
|
372
|
+
return { issues, warnings };
|
|
363
373
|
}
|
|
364
374
|
function validateLearningCorrections(file, status, records, issues) {
|
|
365
375
|
records.forEach((record, index) => {
|
|
@@ -457,7 +467,11 @@ function validateSidecarGroup(inputs, markdown, requireSidecars, requireCritique
|
|
|
457
467
|
if (bundleValue) {
|
|
458
468
|
const claims = Array.isArray(bundleValue.claims) ? bundleValue.claims : [];
|
|
459
469
|
const critiqueClaims = claims.filter((c) => c && c.claimType === "workflow.critique.review");
|
|
460
|
-
|
|
470
|
+
// #282: a historical fail/disputed critique that has been explicitly superseded by a later
|
|
471
|
+
// resolving write (metadata.superseded_by) is retained structurally as history and does NOT
|
|
472
|
+
// block a top-level pass — only a LIVE (non-superseded) fail/disputed critique blocks.
|
|
473
|
+
const isSuperseded = (c) => c && c.metadata && typeof c.metadata === "object" && c.metadata.superseded_by;
|
|
474
|
+
if (critiqueClaims.some((c) => (c.value === "fail" || c.status === "disputed") && !isSuperseded(c)))
|
|
461
475
|
issues.push({ path: trustBundlePath, message: "required critique must pass" });
|
|
462
476
|
}
|
|
463
477
|
}
|
|
@@ -486,12 +500,18 @@ function main() {
|
|
|
486
500
|
const markdown = artifactPaths(pathsIn);
|
|
487
501
|
const sidecars = sidecarPaths(pathsIn);
|
|
488
502
|
const issues = [];
|
|
503
|
+
const warnings = [];
|
|
489
504
|
if (!skipMarkdown)
|
|
490
505
|
for (const file of markdown)
|
|
491
506
|
issues.push(...validateArtifact(file));
|
|
492
|
-
for (const file of sidecars)
|
|
493
|
-
|
|
507
|
+
for (const file of sidecars) {
|
|
508
|
+
const result = validateSidecar(file);
|
|
509
|
+
issues.push(...result.issues);
|
|
510
|
+
warnings.push(...result.warnings);
|
|
511
|
+
}
|
|
494
512
|
issues.push(...validateSidecarGroup(pathsIn, markdown, requireSidecars, requireCritique));
|
|
513
|
+
for (const w of warnings)
|
|
514
|
+
console.error(`WARN ${w.path}: ${w.message}`);
|
|
495
515
|
if (issues.length) {
|
|
496
516
|
for (const issue of issues)
|
|
497
517
|
console.error(`${issue.path}: ${issue.message}`);
|