@kontourai/flow-agents 2.3.0 → 2.4.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 +8 -0
- package/.github/workflows/ci.yml +32 -0
- package/AGENTS.md +4 -4
- package/CHANGELOG.md +39 -0
- package/CONTEXT.md +14 -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/console-learning-projection.js +3 -2
- 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 +22 -6
- 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 +792 -69
- 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 +23 -1
- package/console.telemetry.json +22 -0
- package/context/contracts/artifact-contract.md +38 -7
- package/context/contracts/builder-kit-workflow-state-contract.md +3 -2
- package/context/contracts/decision-registry-contract.md +263 -0
- package/context/contracts/delivery-contract.md +1 -1
- package/context/contracts/planning-contract.md +2 -1
- package/context/contracts/review-contract.md +5 -3
- package/context/contracts/verification-contract.md +9 -1
- package/context/contracts/work-item-contract.md +1 -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/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 +283 -0
- package/docs/agent-system-guidebook.md +3 -3
- package/docs/agent-usage-feedback-loop.md +6 -6
- package/docs/context-map.md +9 -7
- package/docs/decisions/decision-records.md +57 -0
- package/docs/decisions/index.md +14 -0
- package/docs/decisions/promotion-gate.md +52 -0
- package/docs/developer-architecture.md +1 -1
- package/docs/fixture-ownership.md +3 -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 +1 -1
- package/docs/workflow-usage-guide.md +254 -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 +65 -1
- 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_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_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 +2 -2
- package/evals/integration/test_pull_work_liveness_preflight.sh +386 -0
- package/evals/integration/test_pull_work_provider.sh +2 -2
- package/evals/integration/test_reconcile_soundness.sh +4 -4
- 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 +2 -2
- 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 +151 -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 +723 -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 +22 -0
- package/evals/static/test_decisions.sh +150 -0
- package/evals/static/test_flowdef_codeowners_coverage.sh +56 -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 +13 -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 +80 -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/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 +8 -4
- 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/decision-record.schema.json +109 -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 +436 -79
- 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/console-learning-projection.ts +3 -2
- 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 +22 -6
- 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 +795 -71
- 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 +23 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
# install-codex-home.sh - Install Flow Agents
|
|
2
|
+
# install-codex-home.sh - Install Flow Agents into a Codex home.
|
|
3
3
|
set -euo pipefail
|
|
4
4
|
|
|
5
5
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
@@ -19,7 +19,7 @@ Options:
|
|
|
19
19
|
EOF
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
DEST="$HOME/.
|
|
22
|
+
DEST="${CODEX_HOME:-$HOME/.codex}"
|
|
23
23
|
DEST_SET=0
|
|
24
24
|
CONSOLE_CONFIG_ARGS=()
|
|
25
25
|
while [[ $# -gt 0 ]]; do
|
|
@@ -59,47 +59,144 @@ else
|
|
|
59
59
|
fi
|
|
60
60
|
|
|
61
61
|
mkdir -p "$DEST"
|
|
62
|
+
DEST_REAL="$(cd "$DEST" && pwd -P)"
|
|
62
63
|
|
|
63
|
-
|
|
64
|
-
|
|
64
|
+
assert_safe_dest_path() {
|
|
65
|
+
local rel="$1"
|
|
66
|
+
local current="$DEST"
|
|
67
|
+
local part
|
|
68
|
+
IFS='/' read -r -a parts <<< "$rel"
|
|
69
|
+
for part in "${parts[@]}"; do
|
|
70
|
+
[[ -n "$part" && "$part" != "." ]] || continue
|
|
71
|
+
[[ "$part" != ".." ]] || { echo "install-codex-home.sh: unsafe destination path: $rel" >&2; exit 1; }
|
|
72
|
+
current="$current/$part"
|
|
73
|
+
if [[ -L "$current" ]]; then
|
|
74
|
+
echo "install-codex-home.sh: refusing to write through symlink: $current" >&2
|
|
75
|
+
exit 1
|
|
76
|
+
fi
|
|
77
|
+
done
|
|
78
|
+
local parent
|
|
79
|
+
parent="$(dirname "$current")"
|
|
80
|
+
mkdir -p "$parent"
|
|
81
|
+
local parent_real
|
|
82
|
+
parent_real="$(cd "$parent" && pwd -P)"
|
|
83
|
+
case "$parent_real/" in
|
|
84
|
+
"$DEST_REAL"/*) ;;
|
|
85
|
+
*) echo "install-codex-home.sh: destination escapes Codex home: $current" >&2; exit 1 ;;
|
|
86
|
+
esac
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
# Stash the user's existing hooks.json (if any), so the merge step below can
|
|
90
|
+
# preserve user hooks across installs while replacing Flow Agents-managed groups.
|
|
65
91
|
FA_USER_HOOKS_STASH=""
|
|
92
|
+
assert_safe_dest_path "hooks.json"
|
|
66
93
|
if [[ -f "$DEST/hooks.json" ]]; then
|
|
67
94
|
FA_USER_HOOKS_STASH="$(mktemp /tmp/fa-user-hooks.XXXXXX.json)"
|
|
68
95
|
cp "$DEST/hooks.json" "$FA_USER_HOOKS_STASH"
|
|
69
96
|
fi
|
|
70
97
|
|
|
71
|
-
#
|
|
72
|
-
#
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
"$
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
98
|
+
# A real Codex home can contain user-owned config, profiles, auth, hooks, and
|
|
99
|
+
# locally installed kits. Update Flow Agents-owned bundle trees in place without
|
|
100
|
+
# deleting that state. `kits/local` is user/runtime state; everything else under
|
|
101
|
+
# the generated bundle paths is managed by this installer.
|
|
102
|
+
for managed_dir in \
|
|
103
|
+
.flow-agents \
|
|
104
|
+
agent-cards \
|
|
105
|
+
build \
|
|
106
|
+
context \
|
|
107
|
+
docs \
|
|
108
|
+
evals \
|
|
109
|
+
integrations \
|
|
110
|
+
packaging \
|
|
111
|
+
powers \
|
|
112
|
+
prompts \
|
|
113
|
+
schemas \
|
|
114
|
+
scripts
|
|
115
|
+
do
|
|
116
|
+
if [[ -d "$ROOT_DIR/dist/codex/$managed_dir" ]]; then
|
|
117
|
+
assert_safe_dest_path "$managed_dir"
|
|
118
|
+
mkdir -p "$DEST/$managed_dir"
|
|
119
|
+
rsync -a --delete "$ROOT_DIR/dist/codex/$managed_dir/" "$DEST/$managed_dir/"
|
|
120
|
+
fi
|
|
121
|
+
done
|
|
122
|
+
|
|
123
|
+
# Skills are user-extensible in a real Codex home. Merge both bundle skill
|
|
124
|
+
# layers into the flattened destination without deleting user-owned skills.
|
|
125
|
+
if [[ -d "$ROOT_DIR/dist/codex/skills" ]]; then
|
|
126
|
+
assert_safe_dest_path "skills"
|
|
127
|
+
mkdir -p "$DEST/skills"
|
|
128
|
+
rsync -a "$ROOT_DIR/dist/codex/skills/" "$DEST/skills/"
|
|
129
|
+
fi
|
|
130
|
+
|
|
131
|
+
if [[ -d "$ROOT_DIR/dist/codex/.codex/skills" ]]; then
|
|
132
|
+
assert_safe_dest_path "skills"
|
|
133
|
+
mkdir -p "$DEST/skills"
|
|
134
|
+
rsync -a "$ROOT_DIR/dist/codex/.codex/skills/" "$DEST/skills/"
|
|
135
|
+
fi
|
|
136
|
+
|
|
137
|
+
if [[ -d "$ROOT_DIR/dist/codex/kits" ]]; then
|
|
138
|
+
assert_safe_dest_path "kits"
|
|
139
|
+
mkdir -p "$DEST/kits"
|
|
140
|
+
rsync -a --delete --exclude 'local/' "$ROOT_DIR/dist/codex/kits/" "$DEST/kits/"
|
|
141
|
+
fi
|
|
142
|
+
|
|
143
|
+
# Agents are user-extensible in a real Codex home. Merge generated agents
|
|
144
|
+
# without deleting user-owned agents.
|
|
145
|
+
if [[ -d "$ROOT_DIR/dist/codex/.codex/agents" ]]; then
|
|
146
|
+
assert_safe_dest_path "agents"
|
|
147
|
+
mkdir -p "$DEST/agents"
|
|
148
|
+
rsync -a "$ROOT_DIR/dist/codex/.codex/agents/" "$DEST/agents/"
|
|
149
|
+
fi
|
|
150
|
+
|
|
151
|
+
for bundle_file in README.md console.telemetry.json install.sh; do
|
|
152
|
+
if [[ -f "$ROOT_DIR/dist/codex/$bundle_file" ]]; then
|
|
153
|
+
assert_safe_dest_path "$bundle_file"
|
|
154
|
+
cp "$ROOT_DIR/dist/codex/$bundle_file" "$DEST/$bundle_file"
|
|
155
|
+
fi
|
|
156
|
+
done
|
|
157
|
+
|
|
158
|
+
# Root Codex config/profiles and AGENTS.md may be user-owned in ~/.codex.
|
|
159
|
+
# Seed Flow Agents defaults only when absent.
|
|
160
|
+
for seed_file in AGENTS.md; do
|
|
161
|
+
if [[ -f "$ROOT_DIR/dist/codex/$seed_file" && ! -e "$DEST/$seed_file" ]]; then
|
|
162
|
+
assert_safe_dest_path "$seed_file"
|
|
163
|
+
cp "$ROOT_DIR/dist/codex/$seed_file" "$DEST/$seed_file"
|
|
164
|
+
fi
|
|
165
|
+
done
|
|
166
|
+
generated_profile_files=()
|
|
167
|
+
profile_names=()
|
|
168
|
+
for seed_source in "$ROOT_DIR/dist/codex/.codex/config.toml" "$ROOT_DIR"/dist/codex/.codex/*.config.toml; do
|
|
169
|
+
[[ -f "$seed_source" ]] || continue
|
|
170
|
+
seed_file="$(basename "$seed_source")"
|
|
171
|
+
if [[ "$seed_file" == *.config.toml ]]; then
|
|
172
|
+
generated_profile_files+=("$seed_file")
|
|
173
|
+
profile_names+=("${seed_file%.config.toml}")
|
|
174
|
+
fi
|
|
175
|
+
if [[ ! -e "$DEST/$seed_file" ]] || grep -q 'Generated from packaging/manifest.json' "$DEST/$seed_file"; then
|
|
176
|
+
assert_safe_dest_path "$seed_file"
|
|
177
|
+
cp "$seed_source" "$DEST/$seed_file"
|
|
178
|
+
fi
|
|
179
|
+
done
|
|
180
|
+
for profile_path in "$DEST"/*.config.toml; do
|
|
181
|
+
[[ -f "$profile_path" ]] || continue
|
|
182
|
+
profile_file="$(basename "$profile_path")"
|
|
183
|
+
keep_generated=0
|
|
184
|
+
for generated_file in "${generated_profile_files[@]}"; do
|
|
185
|
+
if [[ "$profile_file" == "$generated_file" ]]; then
|
|
186
|
+
keep_generated=1
|
|
187
|
+
break
|
|
188
|
+
fi
|
|
189
|
+
done
|
|
190
|
+
if [[ "$keep_generated" -eq 0 ]] && grep -q 'Generated from packaging/manifest.json' "$profile_path"; then
|
|
191
|
+
assert_safe_dest_path "$profile_file"
|
|
192
|
+
rm -f "$profile_path"
|
|
193
|
+
fi
|
|
194
|
+
done
|
|
195
|
+
rmdir "$DEST/.codex" 2>/dev/null || true
|
|
100
196
|
|
|
101
197
|
for auth_file in auth.json version.json installation_id models_cache.json; do
|
|
102
|
-
if [[ -f "$REAL_CODEX_HOME/$auth_file" ]]; then
|
|
198
|
+
if [[ "$REAL_CODEX_HOME" != "$DEST" && -f "$REAL_CODEX_HOME/$auth_file" ]]; then
|
|
199
|
+
assert_safe_dest_path "$auth_file"
|
|
103
200
|
cp "$REAL_CODEX_HOME/$auth_file" "$DEST/$auth_file"
|
|
104
201
|
fi
|
|
105
202
|
done
|
|
@@ -122,7 +219,7 @@ if command -v node >/dev/null 2>&1 && [[ -f "$FA_MANAGED_HOOKS" ]]; then
|
|
|
122
219
|
--managed-hooks "$FA_MANAGED_HOOKS" \
|
|
123
220
|
--version "$FA_VERSION" \
|
|
124
221
|
--install-record "$DEST/.flow-agents/install.json" \
|
|
125
|
-
--runtime "codex"
|
|
222
|
+
--runtime "codex"
|
|
126
223
|
# Move the merged result into the destination.
|
|
127
224
|
cp "$FA_USER_HOOKS_STASH" "$DEST/hooks.json"
|
|
128
225
|
rm -f "$FA_USER_HOOKS_STASH"
|
|
@@ -133,7 +230,7 @@ if command -v node >/dev/null 2>&1 && [[ -f "$FA_MANAGED_HOOKS" ]]; then
|
|
|
133
230
|
--managed-hooks "$FA_MANAGED_HOOKS" \
|
|
134
231
|
--version "$FA_VERSION" \
|
|
135
232
|
--install-record "$DEST/.flow-agents/install.json" \
|
|
136
|
-
--runtime "codex"
|
|
233
|
+
--runtime "codex"
|
|
137
234
|
fi
|
|
138
235
|
fi
|
|
139
236
|
|
|
@@ -141,5 +238,8 @@ if [[ ${#CONSOLE_CONFIG_ARGS[@]} -gt 0 || -n "${FLOW_AGENTS_TELEMETRY_SINK:-}" |
|
|
|
141
238
|
bash "$DEST/scripts/telemetry/install-console-config.sh" "$DEST/scripts/telemetry/telemetry.conf" "${CONSOLE_CONFIG_ARGS[@]}"
|
|
142
239
|
fi
|
|
143
240
|
|
|
144
|
-
echo "Installed
|
|
145
|
-
|
|
241
|
+
echo "Installed Flow Agents into Codex home at $DEST"
|
|
242
|
+
if [[ "${#profile_names[@]}" -gt 0 ]]; then
|
|
243
|
+
echo "Profiles: ${profile_names[*]}"
|
|
244
|
+
fi
|
|
245
|
+
echo "Use: codex --profile builder"
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Compact Flow Agents workflow status for runtimes with status bar support.
|
|
4
4
|
*
|
|
5
|
-
* Reads optional runtime JSON from stdin, discovers `.flow-agents`, and
|
|
5
|
+
* Reads optional runtime JSON from stdin, discovers `.kontourai/flow-agents`, and
|
|
6
6
|
* prints one ASCII line suitable for Claude Code statusLine or Pi setStatus.
|
|
7
7
|
*/
|
|
8
8
|
|
|
@@ -56,7 +56,7 @@ function runtimeCwd(input) {
|
|
|
56
56
|
function findWorkflowRoot(startDir) {
|
|
57
57
|
let current = path.resolve(startDir || process.cwd());
|
|
58
58
|
for (;;) {
|
|
59
|
-
const candidate = path.join(current, '.flow-agents');
|
|
59
|
+
const candidate = path.join(current, '.kontourai', 'flow-agents');
|
|
60
60
|
if (fs.existsSync(candidate)) return candidate;
|
|
61
61
|
const parent = path.dirname(current);
|
|
62
62
|
if (parent === current) return null;
|
|
@@ -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
|
|
|
7
8
|
type Summary = {
|
|
@@ -24,7 +25,7 @@ function printHelp(): void {
|
|
|
24
25
|
console.log("Build an inert Console learning projection from local workflow learning sidecars.");
|
|
25
26
|
console.log("");
|
|
26
27
|
console.log("Options:");
|
|
27
|
-
console.log(" --artifact-root <path> Workflow artifact root to scan (default: .flow-agents)");
|
|
28
|
+
console.log(" --artifact-root <path> Workflow artifact root to scan (default: .kontourai/flow-agents)");
|
|
28
29
|
console.log(" --kontour-root <path> Local Kontour root to write under (default: .kontour)");
|
|
29
30
|
console.log(" --scope <id> Projection scope id (default: current directory name)");
|
|
30
31
|
console.log(" --scope-kind <kind> Projection scope kind (default: repo)");
|
|
@@ -105,7 +106,7 @@ export function main(argv = process.argv.slice(2)): number {
|
|
|
105
106
|
}
|
|
106
107
|
|
|
107
108
|
try {
|
|
108
|
-
const artifactRoot = path.resolve(flagString(flags, "artifact-root"
|
|
109
|
+
const artifactRoot = path.resolve(flagString(flags, "artifact-root") ?? defaultArtifactRootForRead());
|
|
109
110
|
const kontourRoot = path.resolve(flagString(flags, "kontour-root", ".kontour") ?? ".kontour");
|
|
110
111
|
const producer = requireSafeSegment(flagString(flags, "producer", "flow-agents-learning") ?? "flow-agents-learning", "--producer");
|
|
111
112
|
const scope = {
|
package/src/cli/init.ts
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
|
|
|
14
15
|
type Runtime = "base" | "codex" | "claude-code" | "kiro" | "opencode" | "pi";
|
|
15
16
|
type TelemetrySink = "local-files" | "local-kontour-console" | "kontour-hosted-console" | "user-hosted-console" | "kontour-cloud" | "hosted-kontour-console";
|
|
@@ -101,8 +102,8 @@ Options:
|
|
|
101
102
|
Honors FLOW_AGENTS_USER_CLAUDE_SETTINGS for test isolation.
|
|
102
103
|
opencode: merges opencode.json into ~/.config/opencode/opencode.json
|
|
103
104
|
(honors XDG_CONFIG_HOME; test isolation via FLOW_AGENTS_USER_OPENCODE_CONFIG).
|
|
104
|
-
codex: runs install-codex-home.sh into ~/.
|
|
105
|
-
(
|
|
105
|
+
codex: runs install-codex-home.sh into CODEX_HOME or ~/.codex
|
|
106
|
+
(hooks merged, not overwritten).
|
|
106
107
|
pi: NOT_VERIFIED (no documented global dir); warns and falls back to workspace default.
|
|
107
108
|
--telemetry-sink local-files|local-kontour-console|kontour-hosted-console|user-hosted-console
|
|
108
109
|
--console-url URL
|
|
@@ -193,9 +194,8 @@ function globalDest(runtime: Runtime): string {
|
|
|
193
194
|
return path.join(process.env["XDG_CONFIG_HOME"] ?? path.join(os.homedir(), ".config"), "opencode");
|
|
194
195
|
}
|
|
195
196
|
if (runtime === "codex") {
|
|
196
|
-
// codex --global routes to the
|
|
197
|
-
|
|
198
|
-
return path.join(os.homedir(), ".flow-agents", "codex");
|
|
197
|
+
// codex --global routes to the standard Codex home. --dest remains an explicit override.
|
|
198
|
+
return defaultCodexHome();
|
|
199
199
|
}
|
|
200
200
|
if (runtime === "pi") {
|
|
201
201
|
// pi has no documented global config dir.
|
|
@@ -286,6 +286,73 @@ function ensureBundle(runtime: Runtime): string {
|
|
|
286
286
|
return bundle;
|
|
287
287
|
}
|
|
288
288
|
|
|
289
|
+
// The bundle's hook commands resolve the flow-agents scripts directory via
|
|
290
|
+
// ${CLAUDE_PROJECT_DIR:-$(pwd)}. That is correct for a project-scoped install
|
|
291
|
+
// (installBundle rsyncs scripts/ alongside .claude/, so CLAUDE_PROJECT_DIR ==
|
|
292
|
+
// the install destination). It is NOT correct for a --global install: there
|
|
293
|
+
// is no per-destination copy of scripts/, and CLAUDE_PROJECT_DIR varies with
|
|
294
|
+
// whichever project happens to be open, so the hook resolves to a path that
|
|
295
|
+
// exists in at most one project (and never for most sessions). Global
|
|
296
|
+
// installs need an absolute, session-independent path instead.
|
|
297
|
+
const GLOBAL_INSTALL_PROJECT_DIR_PREFIX = /root="\$\{CLAUDE_PROJECT_DIR:-\$\(pwd\)\}";\s*/g;
|
|
298
|
+
const GLOBAL_INSTALL_PROJECT_DIR_VAR = /"\$root\//g;
|
|
299
|
+
|
|
300
|
+
function rewriteCommandForGlobalInstall(command: string, sourceRoot: string): string {
|
|
301
|
+
return command
|
|
302
|
+
.replace(GLOBAL_INSTALL_PROJECT_DIR_PREFIX, "")
|
|
303
|
+
.replace(GLOBAL_INSTALL_PROJECT_DIR_VAR, `"${sourceRoot}/`);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/** Recursively rewrite every `command` string found under `value` in place. */
|
|
307
|
+
function rewriteCommandsForGlobalInstall(value: unknown, sourceRoot: string): void {
|
|
308
|
+
if (Array.isArray(value)) {
|
|
309
|
+
for (const item of value) rewriteCommandsForGlobalInstall(item, sourceRoot);
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
if (!value || typeof value !== "object") return;
|
|
313
|
+
const obj = value as Record<string, unknown>;
|
|
314
|
+
for (const key of Object.keys(obj)) {
|
|
315
|
+
if (key === "command" && typeof obj[key] === "string") {
|
|
316
|
+
obj[key] = rewriteCommandForGlobalInstall(obj[key] as string, sourceRoot);
|
|
317
|
+
continue;
|
|
318
|
+
}
|
|
319
|
+
rewriteCommandsForGlobalInstall(obj[key], sourceRoot);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Additively copy every file under srcDir into destDir, creating directories
|
|
325
|
+
* as needed and overwriting files whose content changed. Never deletes files
|
|
326
|
+
* in destDir that srcDir does not own — destDir may contain unrelated content
|
|
327
|
+
* (other kits, other tools) that this sync must not touch.
|
|
328
|
+
*/
|
|
329
|
+
function copyDirMerge(srcDir: string, destDir: string): { added: number; updated: number } {
|
|
330
|
+
let added = 0;
|
|
331
|
+
let updated = 0;
|
|
332
|
+
if (!fs.existsSync(srcDir)) return { added, updated };
|
|
333
|
+
for (const entry of fs.readdirSync(srcDir, { withFileTypes: true })) {
|
|
334
|
+
const srcPath = path.join(srcDir, entry.name);
|
|
335
|
+
const destPath = path.join(destDir, entry.name);
|
|
336
|
+
if (entry.isDirectory()) {
|
|
337
|
+
const nested = copyDirMerge(srcPath, destPath);
|
|
338
|
+
added += nested.added;
|
|
339
|
+
updated += nested.updated;
|
|
340
|
+
continue;
|
|
341
|
+
}
|
|
342
|
+
if (!entry.isFile()) continue;
|
|
343
|
+
const content = fs.readFileSync(srcPath);
|
|
344
|
+
if (fs.existsSync(destPath)) {
|
|
345
|
+
if (Buffer.compare(fs.readFileSync(destPath), content) === 0) continue;
|
|
346
|
+
updated += 1;
|
|
347
|
+
} else {
|
|
348
|
+
fs.mkdirSync(path.dirname(destPath), { recursive: true });
|
|
349
|
+
added += 1;
|
|
350
|
+
}
|
|
351
|
+
fs.writeFileSync(destPath, content);
|
|
352
|
+
}
|
|
353
|
+
return { added, updated };
|
|
354
|
+
}
|
|
355
|
+
|
|
289
356
|
function installBundle(bundle: string, options: InitOptions): number {
|
|
290
357
|
const args = ["install.sh", options.dest];
|
|
291
358
|
for (const sink of options.telemetrySinks) args.push("--telemetry-sink", sink);
|
|
@@ -345,10 +412,13 @@ export async function main(argv = process.argv.slice(2)): Promise<number> {
|
|
|
345
412
|
// targeting the global settings, so the collision they'd create is expected.
|
|
346
413
|
if (!options.global) checkScopeCollision();
|
|
347
414
|
}
|
|
348
|
-
// --global for claude-code: merge
|
|
349
|
-
//
|
|
350
|
-
// not
|
|
351
|
-
//
|
|
415
|
+
// --global for claude-code: merge hook-wiring into the global/user-level
|
|
416
|
+
// settings dir, plus additively sync skills/agents so a global install
|
|
417
|
+
// does not silently go stale as new Builder Kit skills ship. This does
|
|
418
|
+
// NOT rsync the full workspace bundle (no context/, powers/, prompts/,
|
|
419
|
+
// etc.) — only the parts a global Claude Code user-config needs. The
|
|
420
|
+
// global settings dir is the claude config root, so the settings.json
|
|
421
|
+
// lives directly in dest (not dest/.claude/).
|
|
352
422
|
if (options.global && options.runtime === "claude-code") {
|
|
353
423
|
const bundle = ensureBundle(options.runtime);
|
|
354
424
|
// For --global, dest is ~/.claude/ (the global settings dir).
|
|
@@ -364,6 +434,10 @@ export async function main(argv = process.argv.slice(2)): Promise<number> {
|
|
|
364
434
|
// Remove permissive defaults (not appropriate for global user settings).
|
|
365
435
|
delete managed["permissions"];
|
|
366
436
|
delete managed["skipDangerousModePermissionPrompt"];
|
|
437
|
+
// See rewriteCommandsForGlobalInstall: bundle hook commands assume
|
|
438
|
+
// CLAUDE_PROJECT_DIR points at this package; a global install must not
|
|
439
|
+
// depend on which project is currently open, so pin to an absolute path.
|
|
440
|
+
rewriteCommandsForGlobalInstall(managed, root);
|
|
367
441
|
fs.mkdirSync(options.dest, { recursive: true });
|
|
368
442
|
const destSettingsPath = path.join(options.dest, "settings.json");
|
|
369
443
|
const installMergePath = path.join(root, "scripts", "install-merge.js");
|
|
@@ -377,16 +451,21 @@ export async function main(argv = process.argv.slice(2)): Promise<number> {
|
|
|
377
451
|
const tmp = `${destSettingsPath}.tmp.${process.pid}`;
|
|
378
452
|
fs.writeFileSync(tmp, `${JSON.stringify(merged, null, 2)}\n`, "utf8");
|
|
379
453
|
fs.renameSync(tmp, destSettingsPath);
|
|
454
|
+
// Additive skills/agents sync: adds new files and updates changed ones,
|
|
455
|
+
// never deletes — dest may hold unrelated content from other kits/tools.
|
|
456
|
+
const skillsSync = copyDirMerge(path.join(bundle, ".claude", "skills"), path.join(options.dest, "skills"));
|
|
457
|
+
const agentsSync = copyDirMerge(path.join(bundle, ".claude", "agents"), path.join(options.dest, "agents"));
|
|
380
458
|
// Write version stamp.
|
|
381
|
-
const
|
|
459
|
+
const recordPath = durableInstallRecordPath(options.dest);
|
|
460
|
+
const installRecordDir = path.dirname(recordPath);
|
|
382
461
|
fs.mkdirSync(installRecordDir, { recursive: true });
|
|
383
462
|
const pkgJson = JSON.parse(fs.readFileSync(path.join(root, "package.json"), "utf8")) as Record<string, string>;
|
|
384
463
|
const record = { version: pkgJson["version"] ?? "0.0.0", installedAt: new Date().toISOString(), runtime: "claude-code", global: true };
|
|
385
|
-
const recordPath = path.join(installRecordDir, "install.json");
|
|
386
464
|
const recordTmp = `${recordPath}.tmp.${process.pid}`;
|
|
387
465
|
fs.writeFileSync(recordTmp, `${JSON.stringify(record, null, 2)}\n`, "utf8");
|
|
388
466
|
fs.renameSync(recordTmp, recordPath);
|
|
389
467
|
console.log(`Flow Agents global hooks merged for claude-code in ${options.dest}`);
|
|
468
|
+
console.log(`Synced skills (+${skillsSync.added} new, ~${skillsSync.updated} updated) and agents (+${agentsSync.added} new, ~${agentsSync.updated} updated) in ${options.dest}`);
|
|
390
469
|
return 0;
|
|
391
470
|
}
|
|
392
471
|
// --global for opencode: merge FA opencode.json into the global opencode config dir.
|
|
@@ -416,11 +495,11 @@ export async function main(argv = process.argv.slice(2)): Promise<number> {
|
|
|
416
495
|
`, "utf8");
|
|
417
496
|
fs.renameSync(tmp, destConfigPath);
|
|
418
497
|
// Write version stamp.
|
|
419
|
-
const
|
|
498
|
+
const recordPath = durableInstallRecordPath(options.dest);
|
|
499
|
+
const installRecordDir = path.dirname(recordPath);
|
|
420
500
|
fs.mkdirSync(installRecordDir, { recursive: true });
|
|
421
501
|
const pkgJson = JSON.parse(fs.readFileSync(path.join(root, "package.json"), "utf8")) as Record<string, string>;
|
|
422
502
|
const record = { version: pkgJson["version"] ?? "0.0.0", installedAt: new Date().toISOString(), runtime: "opencode", global: true };
|
|
423
|
-
const recordPath = path.join(installRecordDir, "install.json");
|
|
424
503
|
const recordTmp = `${recordPath}.tmp.${process.pid}`;
|
|
425
504
|
fs.writeFileSync(recordTmp, `${JSON.stringify(record, null, 2)}
|
|
426
505
|
`, "utf8");
|
|
@@ -428,9 +507,8 @@ export async function main(argv = process.argv.slice(2)): Promise<number> {
|
|
|
428
507
|
console.log(`Flow Agents global config merged for opencode in ${options.dest}`);
|
|
429
508
|
return 0;
|
|
430
509
|
}
|
|
431
|
-
// --global for codex: run install-codex-home.sh to install into the
|
|
432
|
-
//
|
|
433
|
-
// Pass through telemetry/console args and honor --dest override for sandbox testing.
|
|
510
|
+
// --global for codex: run install-codex-home.sh to install into the Codex home.
|
|
511
|
+
// Defaults to CODEX_HOME or ~/.codex. --dest remains an explicit override.
|
|
434
512
|
if (options.global && options.runtime === "codex") {
|
|
435
513
|
const codexHomeScript = path.join(root, "scripts", "install-codex-home.sh");
|
|
436
514
|
if (!fs.existsSync(codexHomeScript)) {
|
|
@@ -448,7 +526,9 @@ export async function main(argv = process.argv.slice(2)): Promise<number> {
|
|
|
448
526
|
console.error(`flow-agents init: unable to run install-codex-home.sh: ${result.error.message}`);
|
|
449
527
|
return 1;
|
|
450
528
|
}
|
|
451
|
-
|
|
529
|
+
const installed = result.status ?? 1;
|
|
530
|
+
if (installed !== 0) return installed;
|
|
531
|
+
return activateKits(options);
|
|
452
532
|
}
|
|
453
533
|
// --global for pi: NOT_VERIFIED (no documented global dir). Warn and fall through to workspace install.
|
|
454
534
|
if (options.global && options.runtime === "pi") {
|
|
@@ -534,11 +614,11 @@ function dogfoodClaudeCode(bundleRoot: string, dest: string): void {
|
|
|
534
614
|
fs.writeFileSync(tmp, `${JSON.stringify(merged, null, 2)}\n`, "utf8");
|
|
535
615
|
fs.renameSync(tmp, destSettingsPath);
|
|
536
616
|
// Write version stamp.
|
|
537
|
-
const
|
|
617
|
+
const recordPath = durableInstallRecordPath(dest);
|
|
618
|
+
const installRecordDir = path.dirname(recordPath);
|
|
538
619
|
fs.mkdirSync(installRecordDir, { recursive: true });
|
|
539
620
|
const pkgJson = JSON.parse(fs.readFileSync(path.join(root, "package.json"), "utf8")) as Record<string, string>;
|
|
540
621
|
const record = { version: pkgJson["version"] ?? "0.0.0", installedAt: new Date().toISOString(), runtime: "claude-code" };
|
|
541
|
-
const recordPath = path.join(installRecordDir, "install.json");
|
|
542
622
|
const recordTmp = `${recordPath}.tmp.${process.pid}`;
|
|
543
623
|
fs.writeFileSync(recordTmp, `${JSON.stringify(record, null, 2)}\n`, "utf8");
|
|
544
624
|
fs.renameSync(recordTmp, recordPath);
|
|
@@ -572,11 +652,11 @@ function dogfoodCodex(bundleRoot: string, dest: string): void {
|
|
|
572
652
|
fs.writeFileSync(tmp, `${JSON.stringify(merged, null, 2)}\n`, "utf8");
|
|
573
653
|
fs.renameSync(tmp, destHooksPath);
|
|
574
654
|
// Write version stamp.
|
|
575
|
-
const
|
|
655
|
+
const recordPath = durableInstallRecordPath(dest);
|
|
656
|
+
const installRecordDir = path.dirname(recordPath);
|
|
576
657
|
fs.mkdirSync(installRecordDir, { recursive: true });
|
|
577
658
|
const pkgJson = JSON.parse(fs.readFileSync(path.join(root, "package.json"), "utf8")) as Record<string, string>;
|
|
578
659
|
const record = { version: pkgJson["version"] ?? "0.0.0", installedAt: new Date().toISOString(), runtime: "codex" };
|
|
579
|
-
const recordPath = path.join(installRecordDir, "install.json");
|
|
580
660
|
const recordTmp = `${recordPath}.tmp.${process.pid}`;
|
|
581
661
|
fs.writeFileSync(recordTmp, `${JSON.stringify(record, null, 2)}\n`, "utf8");
|
|
582
662
|
fs.renameSync(recordTmp, recordPath);
|
package/src/cli/kit.ts
CHANGED
|
@@ -6,14 +6,19 @@ 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
|
|
|
12
13
|
const REGISTRY_REL = path.join("kits", "local", "installed-kits.json");
|
|
13
14
|
const REPOSITORIES_REL = path.join("kits", "local", "repositories");
|
|
14
15
|
|
|
15
16
|
function registryPath(dest: string): string { return path.join(dest, REGISTRY_REL); }
|
|
16
17
|
function installedPath(dest: string, kitId: string): string { return path.join(dest, REPOSITORIES_REL, kitId); }
|
|
18
|
+
function resolveDest(flags: ReturnType<typeof parseArgs>["flags"]): string {
|
|
19
|
+
const explicit = flagString(flags, "dest");
|
|
20
|
+
return path.resolve(explicit ?? defaultCodexHome());
|
|
21
|
+
}
|
|
17
22
|
|
|
18
23
|
function loadRegistry(dest: string): { schema_version: string; kits: Record<string, unknown>[] } {
|
|
19
24
|
const file = registryPath(dest);
|
|
@@ -22,6 +27,28 @@ function loadRegistry(dest: string): { schema_version: string; kits: Record<stri
|
|
|
22
27
|
return { schema_version: data.schema_version ?? "1.0", kits: Array.isArray(data.kits) ? data.kits.filter((item): item is Record<string, unknown> => typeof item === "object" && item !== null) : [] };
|
|
23
28
|
}
|
|
24
29
|
|
|
30
|
+
/**
|
|
31
|
+
* Emit a non-blocking warning for each declared kit dependency that is not present
|
|
32
|
+
* in the destination's LOCAL registry.
|
|
33
|
+
*
|
|
34
|
+
* Scope limitation (v1, accepted): this check only sees the local installed-kits
|
|
35
|
+
* registry at `dest`, NOT the built-in Kit Catalog. A dependency satisfied solely by
|
|
36
|
+
* a built-in catalog kit will still warn here. Presence is enforced authoritatively
|
|
37
|
+
* (hard error) at activation time against the full discovered inventory
|
|
38
|
+
* (built-in catalog + local registry) — see src/runtime-adapters.ts.
|
|
39
|
+
*/
|
|
40
|
+
function warnUninstalledDependencies(manifest: Record<string, unknown>, manifestPath: string, dest: string): void {
|
|
41
|
+
const { entries } = parseKitDependencies(manifest, manifestPath);
|
|
42
|
+
if (!entries.length) return;
|
|
43
|
+
const installed = new Set(loadRegistry(dest).kits.map((entry) => String(entry.id ?? "")));
|
|
44
|
+
const kitId = String(manifest.id);
|
|
45
|
+
for (const dep of entries) {
|
|
46
|
+
if (!installed.has(dep.kit_id)) {
|
|
47
|
+
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`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
25
52
|
function contentHash(root: string): string {
|
|
26
53
|
const hash = crypto.createHash("sha256");
|
|
27
54
|
for (const file of walkFiles(root)) {
|
|
@@ -80,7 +107,7 @@ async function install(argv: string[]): Promise<number> {
|
|
|
80
107
|
|
|
81
108
|
async function installLocalSource(source: string, argv: string[]): Promise<number> {
|
|
82
109
|
const args = parseArgs(argv);
|
|
83
|
-
const dest =
|
|
110
|
+
const dest = resolveDest(args.flags);
|
|
84
111
|
let manifest: Record<string, unknown>;
|
|
85
112
|
try {
|
|
86
113
|
manifest = await assertKitRepository(source);
|
|
@@ -90,10 +117,12 @@ async function installLocalSource(source: string, argv: string[]): Promise<numbe
|
|
|
90
117
|
return 1;
|
|
91
118
|
}
|
|
92
119
|
const kitId = String(manifest.id);
|
|
120
|
+
warnUninstalledDependencies(manifest, path.join(source, "kit.json"), dest);
|
|
93
121
|
const hash = contentHash(source);
|
|
94
122
|
const registry = loadRegistry(dest);
|
|
95
123
|
const existing = registry.kits.find((entry) => entry.id === kitId);
|
|
96
124
|
const target = installedPath(dest, kitId);
|
|
125
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
97
126
|
assertPathContained(dest, target);
|
|
98
127
|
const sourceText = source;
|
|
99
128
|
if (existing && existing.source !== sourceText && !flagBool(args.flags, "update")) {
|
|
@@ -133,7 +162,7 @@ async function installGitSource(rawUrl: string, argv: string[]): Promise<number>
|
|
|
133
162
|
}
|
|
134
163
|
if (!ref) ref = flagString(args.flags, "ref") ?? null;
|
|
135
164
|
|
|
136
|
-
const dest =
|
|
165
|
+
const dest = resolveDest(args.flags);
|
|
137
166
|
const force = flagBool(args.flags, "force") ?? false;
|
|
138
167
|
const update = flagBool(args.flags, "update") ?? false;
|
|
139
168
|
|
|
@@ -167,10 +196,12 @@ async function installGitSource(rawUrl: string, argv: string[]): Promise<number>
|
|
|
167
196
|
|
|
168
197
|
// Delegate to the shared install logic (copy + registry update).
|
|
169
198
|
const kitId = String(manifest.id);
|
|
199
|
+
warnUninstalledDependencies(manifest, path.join(tmpBase, "kit.json"), dest);
|
|
170
200
|
const hash = kitContentHash(tmpBase);
|
|
171
201
|
const registry = loadRegistry(dest);
|
|
172
202
|
const existing = registry.kits.find((entry) => entry.id === kitId);
|
|
173
203
|
const target = installedPath(dest, kitId);
|
|
204
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
174
205
|
assertPathContained(dest, target);
|
|
175
206
|
const sourceText = repoUrl + (ref ? `#${ref}` : "");
|
|
176
207
|
if (existing && existing.source !== sourceText && !update) {
|
|
@@ -202,7 +233,7 @@ async function installGitSource(rawUrl: string, argv: string[]): Promise<number>
|
|
|
202
233
|
|
|
203
234
|
function list(argv: string[]): number {
|
|
204
235
|
const args = parseArgs(argv);
|
|
205
|
-
const dest =
|
|
236
|
+
const dest = resolveDest(args.flags);
|
|
206
237
|
const entries = loadRegistry(dest).kits;
|
|
207
238
|
if (!entries.length) {
|
|
208
239
|
console.log("No local Flow Kits installed.");
|
|
@@ -216,7 +247,7 @@ function list(argv: string[]): number {
|
|
|
216
247
|
|
|
217
248
|
function status(argv: string[]): number {
|
|
218
249
|
const args = parseArgs(argv);
|
|
219
|
-
const dest =
|
|
250
|
+
const dest = resolveDest(args.flags);
|
|
220
251
|
let entries = loadRegistry(dest).kits;
|
|
221
252
|
const kitId = args.positionals[0];
|
|
222
253
|
if (kitId) {
|
|
@@ -241,7 +272,7 @@ const AVAILABLE_ADAPTERS = ["codex-local", "strands-local"];
|
|
|
241
272
|
|
|
242
273
|
function activate(argv: string[]): number {
|
|
243
274
|
const args = parseArgs(argv);
|
|
244
|
-
const dest =
|
|
275
|
+
const dest = resolveDest(args.flags);
|
|
245
276
|
const sourceRoot = path.resolve(flagString(args.flags, "source-root", path.resolve(path.dirname(process.argv[1]), "..")) ?? ".");
|
|
246
277
|
const adapter = flagString(args.flags, "adapter");
|
|
247
278
|
if (adapter && !AVAILABLE_ADAPTERS.includes(adapter)) {
|