@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
|
@@ -39,7 +39,7 @@ This skill owns orchestration and routing. The verification contract owns phases
|
|
|
39
39
|
|
|
40
40
|
## Input
|
|
41
41
|
|
|
42
|
-
- **Session file path**: the session file in `.flow-agents/<slug>/` (preferred)
|
|
42
|
+
- **Session file path**: the session file in `.kontourai/flow-agents/<slug>/` (preferred)
|
|
43
43
|
- The session file references the plan artifact (which has acceptance criteria) and execution progress (which has modified files)
|
|
44
44
|
- If NO session file exists, delegate to tool-verifier directly (see Standalone Verification below)
|
|
45
45
|
|
|
@@ -84,6 +84,13 @@ Skip session file lookup — go straight to delegation.
|
|
|
84
84
|
7. Write or update `evidence.json` with verification checks, top-level verdict, and `not_verified_gaps`
|
|
85
85
|
- use `npm run workflow:sidecar -- record-evidence <artifact-dir> --verdict ... --check-json ...` when the repository provides it
|
|
86
86
|
- `checks[].artifact_refs` must use structured evidence ref objects, not legacy strings
|
|
87
|
+
- for multi-repo or cross-product work, preserve a coverage matrix in the
|
|
88
|
+
evidence report or check summaries that lists each affected root and its
|
|
89
|
+
build/test, dependency/security, provider/CI, and accepted-gap status
|
|
90
|
+
- if external dependency audit or provider checks are blocked by approval,
|
|
91
|
+
privacy, credentials, network, or missing change-provider state, record the
|
|
92
|
+
affected roots as `not_verified` instead of collapsing the lane into a
|
|
93
|
+
generic pass/fail
|
|
87
94
|
8. Update `acceptance.json` with criterion statuses and structured evidence refs
|
|
88
95
|
- `criteria[].evidence_refs` must use structured evidence refs and map each AC id to command/test proof plus source refs for behavior claims
|
|
89
96
|
- when source refs are missing for a behavior claim, mark the criterion `not_verified` or record an accepted gap instead of using broad prose-only evidence
|
package/kits/catalog.json
CHANGED
|
@@ -18,6 +18,12 @@
|
|
|
18
18
|
"name": "Release Evidence Kit",
|
|
19
19
|
"path": "kits/release-evidence",
|
|
20
20
|
"description": "Minimal flows-only kit for proving agentless gate evaluation over surface claims in CI. One gate expects a trusted release.evidence claim."
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"id": "veritas-governance",
|
|
24
|
+
"name": "Veritas Governance Kit",
|
|
25
|
+
"path": "kits/veritas-governance",
|
|
26
|
+
"description": "Agentless kit that gates a real `veritas readiness` verdict as a trust.bundle software-readiness-verdict claim. Wraps @kontourai/veritas via CLI + a kit-local adapter; does not reimplement standards evaluation."
|
|
21
27
|
}
|
|
22
28
|
]
|
|
23
29
|
}
|
package/kits/knowledge/kit.json
CHANGED
|
@@ -91,6 +91,31 @@
|
|
|
91
91
|
"id": "knowledge.obsidian-store",
|
|
92
92
|
"path": "adapters/obsidian-store/index.js",
|
|
93
93
|
"description": "Obsidian store adapter: each record is one human-canonical Obsidian markdown note. Frontmatter carries all contract fields; body rendered with callouts (raw), readable prose + Sources/Related wikilink sections (compiled/concept/snapshot). Category dots map to folder hierarchy; title-slugified filenames with collision suffix; superseded records move to archive/ (supersede-not-delete invariant). Backed by shared codec with default-store."
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
"id": "knowledge.markdown-vault-provider",
|
|
97
|
+
"path": "providers/markdown-vault/index.js",
|
|
98
|
+
"description": "Knowledge store PROVIDER (issue #317): adapts the markdown-vault store behind the typed graph model (nodes/edges/provenance). Thin read + proposals-only wrapper over an existing store adapter; existing skills unaffected."
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
"id": "knowledge.git-repo-provider",
|
|
102
|
+
"path": "providers/git-repo/index.js",
|
|
103
|
+
"description": "Knowledge store PROVIDER (issue #317): reads git-tracked docs (docs/decisions/* tombstones+evidence, CONTEXT.md glossary, docs/learnings/*) as decision/note nodes with supersedes/merged-into/evidence-of edges. Write side = decision-topic proposals shaped for the promote sub-flow."
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
"id": "knowledge.work-item-provider",
|
|
107
|
+
"path": "providers/work-item/index.js",
|
|
108
|
+
"description": "Knowledge store PROVIDER/source-sink adapter (issue #317): maps GitHub issues to issue nodes and flow-agents:work-item-metadata blockers + prose refs to blocks/relates edges via an injectable gh runner. Write side = draft comment/label proposals. Enables backlog hygiene as a knowledge-health pass."
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
"id": "knowledge.health-verbs",
|
|
112
|
+
"path": "providers/health/index.js",
|
|
113
|
+
"description": "Provider-agnostic knowledge-health verbs (issue #317): detectDuplicates + checkDependencyLinkIntegrity over any provider's graph, returning schema-valid health reports (schemas/knowledge/health-report.schema.json)."
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
"id": "knowledge.store-providers",
|
|
117
|
+
"path": "providers/index.js",
|
|
118
|
+
"description": "Public entry point for the knowledge store providers, health verbs, graph model helpers, and schema validator. See context/contracts/knowledge-store-contract.md."
|
|
94
119
|
}
|
|
95
120
|
],
|
|
96
121
|
"evals": [
|
|
@@ -153,6 +178,16 @@
|
|
|
153
178
|
"id": "knowledge.hygiene-review-suite",
|
|
154
179
|
"path": "evals/hygiene-review/suite.test.js",
|
|
155
180
|
"description": "Eval cases for hygiene-review (#106 hygiene #5, closes the issue): opt-in orchestration (omitted audit block is skipped; empty review does nothing); each opted-in audit runs via its existing flow-runner method and its findings are collected verbatim (no detection reimplemented); proposals are normalized to adopt/retire/merge and each cites its sourceFlow + evidence; thin-orchestrator/consume-never-fork invariants — read-only by default mutates no record, no new gate is forked, the only mutation (glossary apply:true) is delegated to glossarySync's own gated propose→apply (verified via proposes-link + mutation-log on the resulting concept); sub-flow telemetry is folded in plus hygiene-review's own orchestrate-gate + review-gate; module-level export delegates to the runner."
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
"id": "knowledge.provider-conformance-suite",
|
|
184
|
+
"path": "providers/conformance/suite.test.js",
|
|
185
|
+
"description": "Conformance suite (issue #317, AC3): parameterized suite every knowledge store provider (markdown-vault, git-repo, work-item) must pass — read interface returns schema-valid nodes/edges with provenance, closed edge vocabulary, queryByType subset consistency, and proposeWrite returns a schema-valid 'proposed' proposal that mutates nothing (proposals-only)."
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
"id": "knowledge.provider-health-pass-suite",
|
|
189
|
+
"path": "providers/health/health-pass.test.js",
|
|
190
|
+
"description": "Health-pass suite (issue #317, AC1+AC2): the same health command over vault + git-repo providers yields schema-valid reports (AC1); a backlog pass over the work-item adapter driven by a seeded fixture flags a duplicate and a broken blocker link (AC2) — never the real board."
|
|
156
191
|
}
|
|
157
192
|
],
|
|
158
193
|
"skills": [
|
package/kits/knowledge/providers/conformance/fixtures/git-repo/docs/decisions/old-sprocket-shape.md
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
---
|
|
2
|
+
status: merged
|
|
3
|
+
subject: Old sprocket shape
|
|
4
|
+
decided: 2026-07-02
|
|
5
|
+
merged_into: sprocket-shape
|
|
6
|
+
evidence:
|
|
7
|
+
- kind: pr
|
|
8
|
+
ref: https://github.com/kontourai/flow-agents/pull/319
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Old sprocket shape
|
|
12
|
+
|
|
13
|
+
Merged into [sprocket-shape](./sprocket-shape.md).
|
package/kits/knowledge/providers/conformance/fixtures/git-repo/docs/decisions/sprocket-shape.md
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
---
|
|
2
|
+
status: current
|
|
3
|
+
subject: Sprocket shape
|
|
4
|
+
decided: 2026-07-02
|
|
5
|
+
supersedes:
|
|
6
|
+
- old-sprocket-shape
|
|
7
|
+
evidence:
|
|
8
|
+
- kind: pr
|
|
9
|
+
ref: https://github.com/kontourai/flow-agents/pull/319
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# Sprocket shape
|
|
13
|
+
|
|
14
|
+
Sprockets are round. Absorbs the old-sprocket-shape topic.
|
package/kits/knowledge/providers/conformance/fixtures/git-repo/docs/decisions/widget-format.md
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
---
|
|
2
|
+
status: current
|
|
3
|
+
subject: Widget format
|
|
4
|
+
decided: 2026-07-01
|
|
5
|
+
evidence:
|
|
6
|
+
- kind: issue
|
|
7
|
+
ref: https://github.com/kontourai/flow-agents/issues/317
|
|
8
|
+
- kind: doc
|
|
9
|
+
ref: docs/learnings/fixture-learning.md
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# Widget format
|
|
13
|
+
|
|
14
|
+
Widgets are stored as JSON. Lean rationale linked via evidence.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"number": 14,
|
|
4
|
+
"title": "Add dark mode toggle to settings",
|
|
5
|
+
"state": "OPEN",
|
|
6
|
+
"labels": [{ "name": "enhancement" }],
|
|
7
|
+
"body": "Users want a dark mode toggle in the settings screen.\n\n<!-- flow-agents:work-item-metadata\n{\n \"schema_version\": \"1.0\",\n \"blockers\": []\n}\n-->"
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"number": 15,
|
|
11
|
+
"title": "Add dark-mode toggle to settings page",
|
|
12
|
+
"state": "OPEN",
|
|
13
|
+
"labels": [{ "name": "enhancement" }],
|
|
14
|
+
"body": "Duplicate of the dark mode toggle request. Settings page needs a dark-mode toggle."
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"number": 20,
|
|
18
|
+
"title": "Ship theme system",
|
|
19
|
+
"state": "OPEN",
|
|
20
|
+
"labels": [{ "name": "feature" }],
|
|
21
|
+
"body": "Overarching theme work. Relates to #14.\n\n<!-- flow-agents:work-item-metadata\n{\n \"schema_version\": \"1.0\",\n \"blockers\": [\n { \"repo\": \"seed/fixture\", \"number\": 14 },\n { \"repo\": \"seed/fixture\", \"number\": 999 }\n ]\n}\n-->"
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"number": 21,
|
|
25
|
+
"title": "Document theme tokens",
|
|
26
|
+
"state": "OPEN",
|
|
27
|
+
"labels": [],
|
|
28
|
+
"body": "Blocked by #20. Also see #14 for the toggle."
|
|
29
|
+
}
|
|
30
|
+
]
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Knowledge Store Provider — Conformance Suite (issue #317, AC3).
|
|
3
|
+
*
|
|
4
|
+
* One parameterized suite every provider must pass. Proves that markdown-vault,
|
|
5
|
+
* git-repo, and work-item all satisfy the read + proposals-only interface and
|
|
6
|
+
* emit schema-valid nodes/edges/proposals. Run:
|
|
7
|
+
* node --test kits/knowledge/providers/conformance/suite.test.js
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { test, describe, before, after } from "node:test";
|
|
11
|
+
import assert from "node:assert/strict";
|
|
12
|
+
import * as fs from "node:fs";
|
|
13
|
+
import * as os from "node:os";
|
|
14
|
+
import * as path from "node:path";
|
|
15
|
+
import { fileURLToPath } from "node:url";
|
|
16
|
+
|
|
17
|
+
import { MarkdownVaultProvider } from "../markdown-vault/index.js";
|
|
18
|
+
import { GitRepoProvider } from "../git-repo/index.js";
|
|
19
|
+
import { WorkItemProvider } from "../work-item/index.js";
|
|
20
|
+
import DefaultKnowledgeStore from "../../adapters/default-store/index.js";
|
|
21
|
+
import { loadSchemas, EDGE_TYPES } from "../lib/model.js";
|
|
22
|
+
import { validate } from "../lib/schema-validate.js";
|
|
23
|
+
|
|
24
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
25
|
+
const FIXTURES = path.join(__dirname, "fixtures");
|
|
26
|
+
const schemas = loadSchemas();
|
|
27
|
+
|
|
28
|
+
function assertSchema(value, schema, label) {
|
|
29
|
+
const { valid, errors } = validate(value, schema);
|
|
30
|
+
assert.ok(valid, `${label} not schema-valid:\n ${errors.join("\n ")}`);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// --- per-provider fixture builders --------------------------------------
|
|
34
|
+
|
|
35
|
+
async function buildVault() {
|
|
36
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "kg-vault-conf-"));
|
|
37
|
+
const store = new DefaultKnowledgeStore({ storeRoot: dir });
|
|
38
|
+
const rawId = await store.create({ type: "raw", title: "Raw source", body: "raw material", category: "eng", provenance: { agent: "test" } });
|
|
39
|
+
const compiledId = await store.create({ type: "compiled", title: "Compiled note", body: "distilled", category: "eng", provenance: { agent: "test" } });
|
|
40
|
+
await store.link(compiledId, [{ target_id: rawId, kind: "source" }], { agent: "test" });
|
|
41
|
+
const provider = new MarkdownVaultProvider({ store, storeRoot: dir, agent: "test" });
|
|
42
|
+
return { provider, proposeIntent: { title: "Proposed note", body: "content", category: "eng" }, cleanup: () => fs.rmSync(dir, { recursive: true, force: true }) };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function buildGitRepo() {
|
|
46
|
+
const provider = new GitRepoProvider({ repoRoot: path.join(FIXTURES, "git-repo"), agent: "test" });
|
|
47
|
+
return { provider, proposeIntent: { subject: "Fixture decision", body: "the answer", evidence: [{ kind: "issue", ref: "#317" }] }, cleanup: () => {} };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function buildWorkItem() {
|
|
51
|
+
const issues = fs.readFileSync(path.join(FIXTURES, "work-item", "issues.json"), "utf8");
|
|
52
|
+
const runner = async () => issues;
|
|
53
|
+
const provider = new WorkItemProvider({ repo: "seed/fixture", runner, agent: "test" });
|
|
54
|
+
return { provider, proposeIntent: { issue: 14, commentBody: "draft comment" }, cleanup: () => {} };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const PROVIDERS = [
|
|
58
|
+
{ name: "markdown-vault", build: buildVault, expectedId: "markdown-vault" },
|
|
59
|
+
{ name: "git-repo", build: buildGitRepo, expectedId: "git-repo" },
|
|
60
|
+
{ name: "work-item", build: buildWorkItem, expectedId: "work-item" },
|
|
61
|
+
];
|
|
62
|
+
|
|
63
|
+
for (const { name, build, expectedId } of PROVIDERS) {
|
|
64
|
+
describe(`conformance: ${name}`, () => {
|
|
65
|
+
let ctx;
|
|
66
|
+
before(async () => { ctx = await build(); });
|
|
67
|
+
after(() => ctx.cleanup());
|
|
68
|
+
|
|
69
|
+
test("capabilities() declares proposals-only, non-writable", () => {
|
|
70
|
+
const cap = ctx.provider.capabilities();
|
|
71
|
+
assert.equal(cap.id, expectedId);
|
|
72
|
+
assert.equal(cap.writable, false);
|
|
73
|
+
assert.equal(cap.write_mode, "proposals-only");
|
|
74
|
+
assert.ok(Array.isArray(cap.proposal_targets) && cap.proposal_targets.length > 0);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test("readNodes() returns schema-valid nodes with unique ids and provider provenance", async () => {
|
|
78
|
+
const nodes = await ctx.provider.readNodes();
|
|
79
|
+
assert.ok(Array.isArray(nodes) && nodes.length > 0, "expected at least one node");
|
|
80
|
+
const ids = new Set();
|
|
81
|
+
for (const n of nodes) {
|
|
82
|
+
assertSchema(n, schemas.node, `${name} node ${n.id}`);
|
|
83
|
+
assert.equal(n.provenance.provider, expectedId);
|
|
84
|
+
assert.ok(!ids.has(n.id), `duplicate node id ${n.id}`);
|
|
85
|
+
ids.add(n.id);
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test("readEdges() returns schema-valid edges in the closed edge vocabulary", async () => {
|
|
90
|
+
const edges = await ctx.provider.readEdges();
|
|
91
|
+
assert.ok(Array.isArray(edges));
|
|
92
|
+
for (const e of edges) {
|
|
93
|
+
assertSchema(e, schemas.edge, `${name} edge ${e.id}`);
|
|
94
|
+
assert.ok(EDGE_TYPES.includes(e.type), `edge type ${e.type} outside closed vocabulary`);
|
|
95
|
+
assert.equal(e.provenance.provider, expectedId);
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test("queryByType() is a consistent subset of readNodes()", async () => {
|
|
100
|
+
const nodes = await ctx.provider.readNodes();
|
|
101
|
+
const types = [...new Set(nodes.map((n) => n.type))];
|
|
102
|
+
for (const t of types) {
|
|
103
|
+
const q = await ctx.provider.queryByType(t);
|
|
104
|
+
assert.ok(q.every((n) => n.type === t), `queryByType(${t}) returned off-type node`);
|
|
105
|
+
assert.equal(q.length, nodes.filter((n) => n.type === t).length);
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test("readGraph() returns { nodes, edges }", async () => {
|
|
110
|
+
const g = await ctx.provider.readGraph();
|
|
111
|
+
assert.ok(Array.isArray(g.nodes) && Array.isArray(g.edges));
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test("proposeWrite() returns a schema-valid 'proposed' proposal and mutates nothing", async () => {
|
|
115
|
+
const before = await ctx.provider.readNodes();
|
|
116
|
+
const p = await ctx.provider.proposeWrite(ctx.proposeIntent);
|
|
117
|
+
assertSchema(p, schemas.proposal, `${name} proposal`);
|
|
118
|
+
assert.equal(p.status, "proposed");
|
|
119
|
+
assert.equal(p.provider, expectedId);
|
|
120
|
+
assert.equal(typeof p.rendered, "string");
|
|
121
|
+
const after = await ctx.provider.readNodes();
|
|
122
|
+
assert.equal(after.length, before.length, "proposeWrite must not add nodes to the store");
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
}
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* git-repo knowledge-store provider.
|
|
3
|
+
*
|
|
4
|
+
* Reads durable git-tracked knowledge as a typed graph:
|
|
5
|
+
* - docs/decisions/<slug>.md -> `decision` nodes; tombstones (superseded_by /
|
|
6
|
+
* merged_into / supersedes[]) -> supersedes / merged-into edges; evidence[]
|
|
7
|
+
* -> evidence-of edges (external, resolved:false).
|
|
8
|
+
* - CONTEXT.md glossary terms -> `note` nodes (vocabulary).
|
|
9
|
+
* - docs/learnings/*.md -> `note` nodes (learnings).
|
|
10
|
+
*
|
|
11
|
+
* Read side is authoritative here. The write side is proposals-only and renders
|
|
12
|
+
* a decision-registry topic file shaped for the promote sub-flow (the same
|
|
13
|
+
* frontmatter schemas/decision-record.schema.json validates) — the provider
|
|
14
|
+
* never writes to disk.
|
|
15
|
+
*
|
|
16
|
+
* @module providers/git-repo
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import * as fs from "node:fs";
|
|
20
|
+
import * as path from "node:path";
|
|
21
|
+
import { node, edge, proposal, provenance } from "../lib/model.js";
|
|
22
|
+
import { parseMarkdown } from "../../adapters/shared/codec.js";
|
|
23
|
+
|
|
24
|
+
const PROVIDER_ID = "git-repo";
|
|
25
|
+
|
|
26
|
+
function slugify(text) {
|
|
27
|
+
return String(text)
|
|
28
|
+
.toLowerCase()
|
|
29
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
30
|
+
.replace(/^-+|-+$/g, "");
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Derive a stable external node id for a decision evidence ref. */
|
|
34
|
+
function evidenceNodeId(kind, ref) {
|
|
35
|
+
const tail = String(ref).replace(/^https?:\/\/github\.com\/[^/]+\/[^/]+\/(issues|pull)\//, "");
|
|
36
|
+
return `${kind}:${slugify(tail).slice(0, 60) || "ref"}`;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export class GitRepoProvider {
|
|
40
|
+
constructor({ repoRoot, agent } = {}) {
|
|
41
|
+
if (!repoRoot) throw new Error("GitRepoProvider requires { repoRoot }");
|
|
42
|
+
this.repoRoot = path.resolve(repoRoot);
|
|
43
|
+
this.agent = agent;
|
|
44
|
+
this.id = PROVIDER_ID;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
capabilities() {
|
|
48
|
+
return {
|
|
49
|
+
id: PROVIDER_ID,
|
|
50
|
+
node_types: ["decision", "note"],
|
|
51
|
+
edge_types: ["supersedes", "merged-into", "evidence-of"],
|
|
52
|
+
writable: false,
|
|
53
|
+
write_mode: "proposals-only",
|
|
54
|
+
proposal_targets: ["decision-topic"],
|
|
55
|
+
source_of_truth: "git-tracked docs (decision registry, CONTEXT.md, learnings)",
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
_decisionsDir() {
|
|
60
|
+
return path.join(this.repoRoot, "docs", "decisions");
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
_readDecisions() {
|
|
64
|
+
const dir = this._decisionsDir();
|
|
65
|
+
if (!fs.existsSync(dir)) return [];
|
|
66
|
+
return fs
|
|
67
|
+
.readdirSync(dir)
|
|
68
|
+
.filter((f) => f.endsWith(".md") && f !== "index.md")
|
|
69
|
+
.map((f) => {
|
|
70
|
+
const slug = f.replace(/\.md$/, "");
|
|
71
|
+
const raw = fs.readFileSync(path.join(dir, f), "utf8");
|
|
72
|
+
const { meta, body } = parseMarkdown(raw);
|
|
73
|
+
return { slug, meta: meta || {}, body, rel: `docs/decisions/${f}` };
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
_readVocabulary() {
|
|
78
|
+
const file = path.join(this.repoRoot, "CONTEXT.md");
|
|
79
|
+
if (!fs.existsSync(file)) return [];
|
|
80
|
+
const text = fs.readFileSync(file, "utf8");
|
|
81
|
+
const lines = text.split("\n");
|
|
82
|
+
const terms = [];
|
|
83
|
+
let inGlossary = false;
|
|
84
|
+
let current = null;
|
|
85
|
+
for (const line of lines) {
|
|
86
|
+
if (/^##\s+Glossary\s*$/.test(line)) { inGlossary = true; continue; }
|
|
87
|
+
if (/^##\s+/.test(line) && !/^##\s+Glossary/.test(line)) { inGlossary = false; }
|
|
88
|
+
if (!inGlossary) continue;
|
|
89
|
+
const h = line.match(/^###\s+(.+?)\s*$/);
|
|
90
|
+
if (h) {
|
|
91
|
+
if (current) terms.push(current);
|
|
92
|
+
current = { term: h[1].trim(), body: "" };
|
|
93
|
+
} else if (current && line.trim() && !line.startsWith("_Avoid_")) {
|
|
94
|
+
if (!current.body) current.body = line.trim();
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
if (current) terms.push(current);
|
|
98
|
+
return terms;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
_readLearnings() {
|
|
102
|
+
const dir = path.join(this.repoRoot, "docs", "learnings");
|
|
103
|
+
if (!fs.existsSync(dir)) return [];
|
|
104
|
+
return fs
|
|
105
|
+
.readdirSync(dir)
|
|
106
|
+
.filter((f) => f.endsWith(".md"))
|
|
107
|
+
.map((f) => {
|
|
108
|
+
const raw = fs.readFileSync(path.join(dir, f), "utf8");
|
|
109
|
+
const { meta, body } = parseMarkdown(raw);
|
|
110
|
+
const title = (meta && meta.title) || f.replace(/\.md$/, "");
|
|
111
|
+
return { file: f, title, body, rel: `docs/learnings/${f}` };
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async readNodes(options = {}) {
|
|
116
|
+
const nodes = [];
|
|
117
|
+
const now = new Date().toISOString();
|
|
118
|
+
|
|
119
|
+
for (const d of this._readDecisions()) {
|
|
120
|
+
const attributes = { registry_status: d.meta.status || "current" };
|
|
121
|
+
if (d.meta.decided) attributes.decided = d.meta.decided;
|
|
122
|
+
nodes.push(
|
|
123
|
+
node({
|
|
124
|
+
id: `decision:${d.slug}`,
|
|
125
|
+
type: "decision",
|
|
126
|
+
title: d.meta.subject || d.slug,
|
|
127
|
+
body: d.body || "",
|
|
128
|
+
attributes,
|
|
129
|
+
provenance: provenance({ provider: PROVIDER_ID, source: d.rel, retrievedAt: now, agent: this.agent }),
|
|
130
|
+
}),
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
for (const v of this._readVocabulary()) {
|
|
135
|
+
nodes.push(
|
|
136
|
+
node({
|
|
137
|
+
id: `vocab:${slugify(v.term)}`,
|
|
138
|
+
type: "note",
|
|
139
|
+
title: v.term,
|
|
140
|
+
body: v.body || "",
|
|
141
|
+
attributes: { kind: "vocabulary" },
|
|
142
|
+
provenance: provenance({ provider: PROVIDER_ID, source: "CONTEXT.md", locator: `glossary#${v.term}`, retrievedAt: now, agent: this.agent }),
|
|
143
|
+
}),
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
for (const l of this._readLearnings()) {
|
|
148
|
+
nodes.push(
|
|
149
|
+
node({
|
|
150
|
+
id: `learning:${slugify(l.file.replace(/\.md$/, ""))}`,
|
|
151
|
+
type: "note",
|
|
152
|
+
title: l.title,
|
|
153
|
+
body: l.body || "",
|
|
154
|
+
attributes: { kind: "learning" },
|
|
155
|
+
provenance: provenance({ provider: PROVIDER_ID, source: l.rel, retrievedAt: now, agent: this.agent }),
|
|
156
|
+
}),
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (options.type) return nodes.filter((n) => n.type === options.type);
|
|
161
|
+
return nodes;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
async readEdges() {
|
|
165
|
+
const edges = [];
|
|
166
|
+
const now = new Date().toISOString();
|
|
167
|
+
for (const d of this._readDecisions()) {
|
|
168
|
+
const self = `decision:${d.slug}`;
|
|
169
|
+
const prov = (loc) => provenance({ provider: PROVIDER_ID, source: d.rel, locator: loc, retrievedAt: now, agent: this.agent });
|
|
170
|
+
|
|
171
|
+
// superseded_by: this subject moved to another slug -> that slug supersedes this
|
|
172
|
+
if (d.meta.superseded_by) {
|
|
173
|
+
edges.push(edge({ id: `${d.meta.superseded_by}--supersedes--${d.slug}`, type: "supersedes", from: `decision:${d.meta.superseded_by}`, to: self, provenance: prov("superseded_by") }));
|
|
174
|
+
}
|
|
175
|
+
// merged_into: this folded into another slug
|
|
176
|
+
if (d.meta.merged_into) {
|
|
177
|
+
edges.push(edge({ id: `${d.slug}--merged-into--${d.meta.merged_into}`, type: "merged-into", from: self, to: `decision:${d.meta.merged_into}`, provenance: prov("merged_into") }));
|
|
178
|
+
}
|
|
179
|
+
// supersedes[]: this file absorbed those slugs
|
|
180
|
+
for (const absorbed of Array.isArray(d.meta.supersedes) ? d.meta.supersedes : []) {
|
|
181
|
+
edges.push(edge({ id: `${d.slug}--supersedes--${absorbed}`, type: "supersedes", from: self, to: `decision:${absorbed}`, provenance: prov("supersedes") }));
|
|
182
|
+
}
|
|
183
|
+
// evidence[]: external provenance -> evidence-of (resolved:false, external ref)
|
|
184
|
+
for (const ev of Array.isArray(d.meta.evidence) ? d.meta.evidence : []) {
|
|
185
|
+
if (!ev || !ev.kind || !ev.ref) continue;
|
|
186
|
+
edges.push(edge({ id: `${d.slug}--evidence-of--${evidenceNodeId(ev.kind, ev.ref)}`, type: "evidence-of", from: self, to: evidenceNodeId(ev.kind, ev.ref), resolved: false, attributes: { evidence_kind: ev.kind, ref: ev.ref }, provenance: prov("evidence") }));
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return edges;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
async queryByType(type) {
|
|
193
|
+
return this.readNodes({ type });
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
async readGraph() {
|
|
197
|
+
const [nodes, edges] = await Promise.all([this.readNodes(), this.readEdges()]);
|
|
198
|
+
return { nodes, edges };
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Propose a decision-registry topic file. Renders frontmatter compatible with
|
|
203
|
+
* schemas/decision-record.schema.json + the promote sub-flow. Never writes.
|
|
204
|
+
*/
|
|
205
|
+
async proposeWrite(intent = {}) {
|
|
206
|
+
const { subject, body = "", decided, evidence = [] } = intent;
|
|
207
|
+
if (!subject) throw new Error("git-repo proposeWrite requires intent.subject");
|
|
208
|
+
const slug = intent.slug || slugify(subject);
|
|
209
|
+
const evLines = (evidence.length ? evidence : [{ kind: "doc", ref: "TODO: link durable provenance" }])
|
|
210
|
+
.flatMap((e) => [` - kind: ${e.kind}`, ` ref: ${e.ref}`]);
|
|
211
|
+
const rendered = [
|
|
212
|
+
"---",
|
|
213
|
+
"status: current",
|
|
214
|
+
`subject: ${subject}`,
|
|
215
|
+
`decided: ${decided || new Date().toISOString().slice(0, 10)}`,
|
|
216
|
+
"evidence:",
|
|
217
|
+
...evLines,
|
|
218
|
+
"---",
|
|
219
|
+
"",
|
|
220
|
+
`# ${subject}`,
|
|
221
|
+
"",
|
|
222
|
+
body,
|
|
223
|
+
].join("\n");
|
|
224
|
+
return proposal({
|
|
225
|
+
provider: PROVIDER_ID,
|
|
226
|
+
kind: "decision-topic",
|
|
227
|
+
target: { path: `docs/decisions/${slug}.md`, slug },
|
|
228
|
+
payload: { subject, slug, decided, evidence, body },
|
|
229
|
+
rendered,
|
|
230
|
+
rationale: intent.rationale || "Proposed decision topic — enact via the promote sub-flow (write docs/decisions/<slug>.md, add the subject noun to CONTEXT.md, run check:decisions).",
|
|
231
|
+
provenance: provenance({ provider: PROVIDER_ID, source: `docs/decisions/${slug}.md`, agent: this.agent }),
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export default GitRepoProvider;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provider-agnostic health verbs — AC1 + AC2 (issue #317).
|
|
3
|
+
*
|
|
4
|
+
* AC1: the SAME health command over the vault provider and the git-repo
|
|
5
|
+
* provider yields schema-valid reports (R1, R2, R4).
|
|
6
|
+
* AC2: a backlog pass over the work-item adapter flags a SEEDED duplicate and
|
|
7
|
+
* a SEEDED broken blocker link, from a fixture — never the real board
|
|
8
|
+
* (R3, R4).
|
|
9
|
+
*
|
|
10
|
+
* Run: node --test kits/knowledge/providers/health/health-pass.test.js
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { test, describe } from "node:test";
|
|
14
|
+
import assert from "node:assert/strict";
|
|
15
|
+
import * as fs from "node:fs";
|
|
16
|
+
import * as os from "node:os";
|
|
17
|
+
import * as path from "node:path";
|
|
18
|
+
import { fileURLToPath } from "node:url";
|
|
19
|
+
|
|
20
|
+
import { MarkdownVaultProvider } from "../markdown-vault/index.js";
|
|
21
|
+
import { GitRepoProvider } from "../git-repo/index.js";
|
|
22
|
+
import { WorkItemProvider } from "../work-item/index.js";
|
|
23
|
+
import DefaultKnowledgeStore from "../../adapters/default-store/index.js";
|
|
24
|
+
import { detectDuplicates, checkDependencyLinkIntegrity } from "./index.js";
|
|
25
|
+
import { loadSchemas } from "../lib/model.js";
|
|
26
|
+
import { validate } from "../lib/schema-validate.js";
|
|
27
|
+
|
|
28
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
29
|
+
const FIXTURES = path.join(__dirname, "../conformance/fixtures");
|
|
30
|
+
const { healthReport: REPORT_SCHEMA } = loadSchemas();
|
|
31
|
+
|
|
32
|
+
function assertReport(report, expectCheck, expectProvider) {
|
|
33
|
+
const { valid, errors } = validate(report, REPORT_SCHEMA);
|
|
34
|
+
assert.ok(valid, `report not schema-valid:\n ${errors.join("\n ")}`);
|
|
35
|
+
assert.equal(report.check, expectCheck);
|
|
36
|
+
assert.equal(report.provider, expectProvider);
|
|
37
|
+
assert.equal(report.summary.finding_count, report.findings.length);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async function vaultGraph() {
|
|
41
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "kg-vault-health-"));
|
|
42
|
+
const store = new DefaultKnowledgeStore({ storeRoot: dir });
|
|
43
|
+
const a = await store.create({ type: "compiled", title: "Caching strategy", body: "use LRU", category: "eng", provenance: { agent: "t" } });
|
|
44
|
+
const b = await store.create({ type: "compiled", title: "Caching strategy", body: "use LRU everywhere", category: "eng", provenance: { agent: "t" } });
|
|
45
|
+
await store.link(b, [{ target_id: a, kind: "refines" }], { agent: "t" });
|
|
46
|
+
const provider = new MarkdownVaultProvider({ store, storeRoot: dir, agent: "t" });
|
|
47
|
+
const graph = await provider.readGraph();
|
|
48
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
49
|
+
return graph;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
describe("AC1: same health command over two providers yields schema-valid reports", () => {
|
|
53
|
+
test("vault provider: duplicate + integrity reports are schema-valid", async () => {
|
|
54
|
+
const graph = await vaultGraph();
|
|
55
|
+
assertReport(detectDuplicates(graph, { provider: "markdown-vault" }), "duplicate-detection", "markdown-vault");
|
|
56
|
+
assertReport(checkDependencyLinkIntegrity(graph, { provider: "markdown-vault" }), "dependency-link-integrity", "markdown-vault");
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test("git-repo provider: duplicate + integrity reports are schema-valid", async () => {
|
|
60
|
+
const provider = new GitRepoProvider({ repoRoot: path.join(FIXTURES, "git-repo"), agent: "t" });
|
|
61
|
+
const graph = await provider.readGraph();
|
|
62
|
+
assertReport(detectDuplicates(graph, { provider: "git-repo" }), "duplicate-detection", "git-repo");
|
|
63
|
+
assertReport(checkDependencyLinkIntegrity(graph, { provider: "git-repo" }), "dependency-link-integrity", "git-repo");
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
describe("AC2: backlog pass over the work-item adapter flags seeded dupe + broken blocker", () => {
|
|
68
|
+
async function workItemGraph() {
|
|
69
|
+
const issues = fs.readFileSync(path.join(FIXTURES, "work-item", "issues.json"), "utf8");
|
|
70
|
+
const provider = new WorkItemProvider({ repo: "seed/fixture", runner: async () => issues, agent: "t" });
|
|
71
|
+
return provider.readGraph();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
test("duplicate detection flags the seeded #14 ~ #15 pair", async () => {
|
|
75
|
+
const graph = await workItemGraph();
|
|
76
|
+
const report = detectDuplicates(graph, { provider: "work-item" });
|
|
77
|
+
assertReport(report, "duplicate-detection", "work-item");
|
|
78
|
+
const dupe = report.findings.find(
|
|
79
|
+
(f) => f.node_ids.includes("issue:14") && f.node_ids.includes("issue:15"),
|
|
80
|
+
);
|
|
81
|
+
assert.ok(dupe, "expected a duplicate finding for issue:14 ~ issue:15");
|
|
82
|
+
assert.equal(dupe.kind, "duplicate-nodes");
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test("dependency-link integrity flags the seeded broken blocker (#999 -> #20)", async () => {
|
|
86
|
+
const graph = await workItemGraph();
|
|
87
|
+
const report = checkDependencyLinkIntegrity(graph, { provider: "work-item" });
|
|
88
|
+
assertReport(report, "dependency-link-integrity", "work-item");
|
|
89
|
+
const broken = report.findings.find(
|
|
90
|
+
(f) => f.node_ids.includes("issue:999") && f.node_ids.includes("issue:20"),
|
|
91
|
+
);
|
|
92
|
+
assert.ok(broken, "expected a broken-blocker finding for issue:999 -> issue:20");
|
|
93
|
+
assert.equal(broken.kind, "broken-dependency-link");
|
|
94
|
+
assert.equal(broken.severity, "error");
|
|
95
|
+
// the valid blocker #14 -> #20 must NOT be flagged
|
|
96
|
+
const falsePositive = report.findings.find((f) => f.node_ids.includes("issue:14") && f.node_ids.includes("issue:20"));
|
|
97
|
+
assert.ok(!falsePositive, "valid blocker #14 -> #20 must not be flagged");
|
|
98
|
+
});
|
|
99
|
+
});
|