@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.
Files changed (301) hide show
  1. package/.github/CODEOWNERS +16 -0
  2. package/.github/workflows/ci.yml +44 -0
  3. package/.github/workflows/trust-reconcile.yml +62 -4
  4. package/AGENTS.md +4 -4
  5. package/CHANGELOG.md +62 -0
  6. package/CONTEXT.md +35 -0
  7. package/README.md +9 -3
  8. package/agents/dev.json +1 -1
  9. package/agents/tool-code-reviewer.json +1 -1
  10. package/agents/tool-planner.json +3 -3
  11. package/agents/tool-verifier.json +3 -3
  12. package/build/src/cli/assignment-provider.d.ts +1 -0
  13. package/build/src/cli/assignment-provider.js +748 -0
  14. package/build/src/cli/console-learning-projection.js +3 -2
  15. package/build/src/cli/effective-assignment-provider-settings.d.ts +1 -0
  16. package/build/src/cli/effective-assignment-provider-settings.js +125 -0
  17. package/build/src/cli/init.js +104 -21
  18. package/build/src/cli/kit.js +37 -6
  19. package/build/src/cli/pull-work-provider.js +1 -1
  20. package/build/src/cli/usage-feedback.js +3 -3
  21. package/build/src/cli/validate-hook-influence.js +1 -0
  22. package/build/src/cli/validate-workflow-artifacts.js +27 -7
  23. package/build/src/cli/workflow-artifact-cleanup-audit.js +36 -2
  24. package/build/src/cli/workflow-sidecar.d.ts +14 -7
  25. package/build/src/cli/workflow-sidecar.js +938 -168
  26. package/build/src/cli.js +6 -0
  27. package/build/src/flow-kit/validate.d.ts +23 -0
  28. package/build/src/flow-kit/validate.js +67 -1
  29. package/build/src/index.d.ts +1 -1
  30. package/build/src/index.js +1 -1
  31. package/build/src/lib/flow-resolver.d.ts +6 -4
  32. package/build/src/lib/flow-resolver.js +125 -58
  33. package/build/src/lib/local-artifact-root.d.ts +14 -2
  34. package/build/src/lib/local-artifact-root.js +22 -5
  35. package/build/src/lib/workflow-learning-projection.js +2 -2
  36. package/build/src/runtime-adapters.d.ts +12 -0
  37. package/build/src/runtime-adapters.js +56 -16
  38. package/build/src/tools/build-universal-bundles.js +49 -16
  39. package/build/src/tools/generate-context-map.js +10 -8
  40. package/build/src/tools/validate-source-tree.js +24 -1
  41. package/console.telemetry.json +22 -0
  42. package/context/contracts/artifact-contract.md +40 -7
  43. package/context/contracts/assignment-provider-contract.md +239 -0
  44. package/context/contracts/builder-kit-workflow-state-contract.md +5 -2
  45. package/context/contracts/decision-registry-contract.md +265 -0
  46. package/context/contracts/delivery-contract.md +3 -1
  47. package/context/contracts/execution-contract.md +25 -0
  48. package/context/contracts/governance-adapter-contract.md +2 -0
  49. package/context/contracts/knowledge-store-contract.md +197 -0
  50. package/context/contracts/planning-contract.md +4 -1
  51. package/context/contracts/review-contract.md +7 -3
  52. package/context/contracts/sandbox-policy.md +2 -0
  53. package/context/contracts/standing-directives.md +13 -0
  54. package/context/contracts/verification-contract.md +11 -1
  55. package/context/contracts/work-item-contract.md +3 -1
  56. package/context/deferred/parallelization.md +1 -1
  57. package/context/gate-awareness.md +4 -4
  58. package/context/scripts/git-status.sh +2 -2
  59. package/context/scripts/hooks/config-protection.js +502 -2
  60. package/context/scripts/hooks/lib/config-protection-remedies.js +69 -0
  61. package/context/scripts/hooks/run-hook.js +14 -1
  62. package/context/scripts/hooks/stop-goal-fit.js +1140 -99
  63. package/context/scripts/hooks/workflow-steering.js +212 -6
  64. package/context/settings/assignment-provider-settings.json +33 -0
  65. package/docs/adr/0007-flow-skill-kit-tool-boundary.md +2 -0
  66. package/docs/adr/0015-flow-flow-agents-boundary-reconciliation.md +35 -0
  67. package/docs/adr/0019-kit-dependency-ownership.md +62 -0
  68. package/docs/adr/0020-trust-reconcile-manifest-and-claim-classification.md +290 -0
  69. package/docs/adr/0021-assignment-leases-and-stale-claim-takeover.md +221 -0
  70. package/docs/adr/0022-fail-closed-delivery-reconciliation-with-governed-exemptions.md +463 -0
  71. package/docs/agent-system-guidebook.md +3 -3
  72. package/docs/agent-usage-feedback-loop.md +6 -6
  73. package/docs/context-map.md +10 -7
  74. package/docs/decisions/decision-records.md +57 -0
  75. package/docs/decisions/index.md +17 -0
  76. package/docs/decisions/knowledge-store-provider.md +51 -0
  77. package/docs/decisions/model-routing.md +63 -0
  78. package/docs/decisions/promotion-gate.md +52 -0
  79. package/docs/decisions/standing-directives.md +66 -0
  80. package/docs/developer-architecture.md +1 -1
  81. package/docs/fixture-ownership.md +4 -0
  82. package/docs/flow-kit-repository-contract.md +5 -5
  83. package/docs/getting-started.md +12 -2
  84. package/docs/integrations/harness-install.md +2 -1
  85. package/docs/kit-authoring-guide.md +64 -5
  86. package/docs/knowledge-kit.md +5 -1
  87. package/docs/learnings/2026-07-improvement-program.md +203 -0
  88. package/docs/migrations.md +6 -5
  89. package/docs/north-star.md +2 -2
  90. package/docs/operating-layers.md +4 -3
  91. package/docs/repository-structure.md +5 -5
  92. package/docs/skills-map.md +8 -8
  93. package/docs/spec/runtime-hook-surface.md +19 -15
  94. package/docs/spikes/graph-provider-2026-07.md +155 -0
  95. package/docs/standards-register.md +7 -7
  96. package/docs/trust-anchor-adoption.md +105 -4
  97. package/docs/work-item-adapters.md +1 -1
  98. package/docs/workflow-artifact-lifecycle.md +72 -9
  99. package/docs/workflow-eval-strategy.md +3 -3
  100. package/docs/workflow-shared-contracts.md +3 -2
  101. package/docs/workflow-usage-guide.md +260 -21
  102. package/evals/acceptance/DEMO-false-completion.md +20 -11
  103. package/evals/acceptance/prove-capture-teeth-declared.sh +15 -15
  104. package/evals/acceptance/prove-capture-teeth.sh +11 -11
  105. package/evals/acceptance/test_kiro_harness.sh +1 -1
  106. package/evals/ci/antigaming-suite.sh +5 -0
  107. package/evals/ci/run-baseline.sh +71 -1
  108. package/evals/fixtures/assignment-provider/actor-a.json +6 -0
  109. package/evals/fixtures/assignment-provider/actor-b.json +6 -0
  110. package/evals/fixtures/assignment-provider/github-issue-claimed.json +27 -0
  111. package/evals/fixtures/assignment-provider/github-issue-unassigned.json +7 -0
  112. package/evals/fixtures/assignment-provider/liveness-fresh.json +9 -0
  113. package/evals/fixtures/assignment-provider/liveness-stale.json +9 -0
  114. package/evals/fixtures/flow-kit-repository/invalid-bad-dependency/flows/review.flow.json +26 -0
  115. package/evals/fixtures/flow-kit-repository/invalid-bad-dependency/kit.json +13 -0
  116. package/evals/fixtures/flow-kit-repository/valid-with-dependency/flows/review.flow.json +26 -0
  117. package/evals/fixtures/flow-kit-repository/valid-with-dependency/kit.json +13 -0
  118. package/evals/fixtures/hook-influence/cases.json +32 -0
  119. package/evals/fixtures/pull-work-provider/github-issues.json +6 -6
  120. package/evals/fixtures/trust-reconcile-exploits/fabricated-attestation.json +59 -0
  121. package/evals/fixtures/trust-reconcile-exploits/no-label-bypass.json +36 -0
  122. package/evals/fixtures/trust-reconcile-exploits/skip-assumed-bypass.json +59 -0
  123. package/evals/fixtures/trust-reconcile-exploits/status-misassertion.json +48 -0
  124. package/evals/fixtures/trust-reconcile-exploits/waived-command-check.json +44 -0
  125. package/evals/fixtures/trust-reconcile-mixed-bundle/mixed-bundle.json +159 -0
  126. package/evals/fixtures/trust-reconcile-ws3/ws3-bundle.json +920 -0
  127. package/evals/integration/test_actor_identity.sh +254 -0
  128. package/evals/integration/test_assignment_provider_github.sh +318 -0
  129. package/evals/integration/test_assignment_provider_local_file.sh +222 -0
  130. package/evals/integration/test_builder_step_producers.sh +33 -23
  131. package/evals/integration/test_bundle_install.sh +22 -16
  132. package/evals/integration/test_bundle_lifecycle.sh +11 -11
  133. package/evals/integration/test_captured_fail_reconciliation.sh +50 -50
  134. package/evals/integration/test_checkpoint_signing.sh +43 -0
  135. package/evals/integration/test_claim_lookup.sh +4 -4
  136. package/evals/integration/test_codex_hook_resolution.sh +114 -0
  137. package/evals/integration/test_command_log_concurrency.sh +8 -8
  138. package/evals/integration/test_command_log_fork_classification.sh +3 -3
  139. package/evals/integration/test_command_log_integrity.sh +16 -16
  140. package/evals/integration/test_console_learning_projection.sh +1 -1
  141. package/evals/integration/test_critique_supersession_roundtrip.sh +182 -0
  142. package/evals/integration/test_dual_emit_flow_step.sh +145 -0
  143. package/evals/integration/test_enforcer_expects_driven.sh +12 -12
  144. package/evals/integration/test_evidence_capture_hook.sh +14 -13
  145. package/evals/integration/test_fixture_retirement_audit.sh +2 -2
  146. package/evals/integration/test_flow_agents_statusline.sh +2 -2
  147. package/evals/integration/test_flow_kit_install_git.sh +52 -0
  148. package/evals/integration/test_flow_kit_repository.sh +2 -0
  149. package/evals/integration/test_flowdef_session_activation.sh +6 -6
  150. package/evals/integration/test_flowdef_union_floor_regression.sh +429 -0
  151. package/evals/integration/test_gate_bypass_chain.sh +52 -40
  152. package/evals/integration/test_gate_lockdown.sh +97 -73
  153. package/evals/integration/test_gate_review_inquiry_records.sh +3 -3
  154. package/evals/integration/test_goal_fit_escape_hatch.sh +10 -10
  155. package/evals/integration/test_goal_fit_ghost_session.sh +104 -0
  156. package/evals/integration/test_goal_fit_hook.sh +32 -32
  157. package/evals/integration/test_goal_fit_rederive.sh +9 -9
  158. package/evals/integration/test_hook_category_behaviors.sh +103 -0
  159. package/evals/integration/test_hook_influence_cases.sh +1 -0
  160. package/evals/integration/test_install_merge.sh +262 -1
  161. package/evals/integration/test_kit_identity_trust.sh +6 -6
  162. package/evals/integration/test_liveness_conflict_injection.sh +587 -0
  163. package/evals/integration/test_liveness_heartbeat.sh +677 -0
  164. package/evals/integration/test_liveness_verdict.sh +394 -0
  165. package/evals/integration/test_local_flow_kit_install.sh +15 -0
  166. package/evals/integration/test_phase_map_and_gate_claim.sh +63 -11
  167. package/evals/integration/test_promote_gate.sh +178 -0
  168. package/evals/integration/test_publish_delivery.sh +23 -6
  169. package/evals/integration/test_pull_work_assignment_join.sh +132 -0
  170. package/evals/integration/test_pull_work_liveness_preflight.sh +394 -0
  171. package/evals/integration/test_pull_work_provider.sh +2 -2
  172. package/evals/integration/test_reconcile_soundness.sh +37 -13
  173. package/evals/integration/test_resolvefirststep_security.sh +22 -0
  174. package/evals/integration/test_runtime_adapter_activation.sh +87 -18
  175. package/evals/integration/test_session_resume_roundtrip.sh +147 -0
  176. package/evals/integration/test_sidecar_field_preservation.sh +249 -0
  177. package/evals/integration/test_trust_reconcile.sh +11 -10
  178. package/evals/integration/test_trust_reconcile_manifest.sh +79 -0
  179. package/evals/integration/test_trust_reconcile_mixed_bundle.sh +96 -0
  180. package/evals/integration/test_trust_reconcile_negatives.sh +759 -0
  181. package/evals/integration/test_usage_feedback_global.sh +5 -5
  182. package/evals/integration/test_validate_artifacts_portability.sh +60 -0
  183. package/evals/integration/test_verify_cli.sh +2 -2
  184. package/evals/integration/test_veritas_governance_kit.sh +117 -0
  185. package/evals/integration/test_workflow_artifact_cleanup_audit.sh +26 -1
  186. package/evals/integration/test_workflow_artifacts.sh +33 -0
  187. package/evals/integration/test_workflow_sidecar_writer.sh +802 -74
  188. package/evals/integration/test_workflow_steering_hook.sh +175 -5
  189. package/evals/lib/codex-provider.sh +1 -1
  190. package/evals/lib/node.sh +19 -1
  191. package/evals/run.sh +32 -0
  192. package/evals/static/test_decisions.sh +150 -0
  193. package/evals/static/test_flowdef_codeowners_coverage.sh +62 -0
  194. package/evals/static/test_knowledge_providers.sh +23 -0
  195. package/evals/static/test_package.sh +19 -10
  196. package/evals/static/test_universal_bundles.sh +48 -3
  197. package/evals/static/test_workflow_skills.sh +41 -8
  198. package/kits/builder/flows/build.flow.json +3 -74
  199. package/kits/builder/flows/publish-learn.flow.json +90 -0
  200. package/kits/builder/kit.json +11 -0
  201. package/kits/builder/skills/builder-shape/SKILL.md +1 -1
  202. package/kits/builder/skills/deliver/SKILL.md +32 -11
  203. package/kits/builder/skills/design-probe/SKILL.md +4 -4
  204. package/kits/builder/skills/evidence-gate/SKILL.md +26 -1
  205. package/kits/builder/skills/execute-plan/SKILL.md +2 -2
  206. package/kits/builder/skills/fix-bug/SKILL.md +2 -0
  207. package/kits/builder/skills/gate-review/SKILL.md +6 -6
  208. package/kits/builder/skills/idea-to-backlog/SKILL.md +1 -1
  209. package/kits/builder/skills/learning-review/SKILL.md +7 -7
  210. package/kits/builder/skills/pickup-probe/SKILL.md +5 -4
  211. package/kits/builder/skills/plan-work/SKILL.md +27 -5
  212. package/kits/builder/skills/pull-work/SKILL.md +148 -5
  213. package/kits/builder/skills/release-readiness/SKILL.md +4 -4
  214. package/kits/builder/skills/review-work/SKILL.md +24 -1
  215. package/kits/builder/skills/tdd-workflow/SKILL.md +2 -0
  216. package/kits/builder/skills/verify-work/SKILL.md +8 -1
  217. package/kits/catalog.json +6 -0
  218. package/kits/knowledge/kit.json +35 -0
  219. package/kits/knowledge/providers/conformance/fixtures/git-repo/CONTEXT.md +12 -0
  220. package/kits/knowledge/providers/conformance/fixtures/git-repo/docs/decisions/old-sprocket-shape.md +13 -0
  221. package/kits/knowledge/providers/conformance/fixtures/git-repo/docs/decisions/sprocket-shape.md +14 -0
  222. package/kits/knowledge/providers/conformance/fixtures/git-repo/docs/decisions/widget-format.md +14 -0
  223. package/kits/knowledge/providers/conformance/fixtures/git-repo/docs/learnings/fixture-learning.md +7 -0
  224. package/kits/knowledge/providers/conformance/fixtures/work-item/issues.json +30 -0
  225. package/kits/knowledge/providers/conformance/suite.test.js +125 -0
  226. package/kits/knowledge/providers/git-repo/index.js +236 -0
  227. package/kits/knowledge/providers/health/health-pass.test.js +99 -0
  228. package/kits/knowledge/providers/health/index.js +153 -0
  229. package/kits/knowledge/providers/index.js +24 -0
  230. package/kits/knowledge/providers/lib/model.js +91 -0
  231. package/kits/knowledge/providers/lib/schema-validate.js +119 -0
  232. package/kits/knowledge/providers/markdown-vault/index.js +169 -0
  233. package/kits/knowledge/providers/work-item/index.js +204 -0
  234. package/kits/veritas-governance/adapter/readiness-to-trust-bundle.mjs +178 -0
  235. package/kits/veritas-governance/docs/README.md +75 -0
  236. package/kits/veritas-governance/fixtures/readiness/not-ready.readiness-report.json +4645 -0
  237. package/kits/veritas-governance/fixtures/readiness/ready.readiness-report.json +4403 -0
  238. package/kits/veritas-governance/flows/readiness-check.flow.json +35 -0
  239. package/kits/veritas-governance/kit.json +16 -0
  240. package/package.json +11 -5
  241. package/packaging/README.md +2 -2
  242. package/packaging/conformance/fixtures/config-protection--allow-read-state-json.json +20 -0
  243. package/packaging/conformance/fixtures/config-protection--block-write-state-json.json +20 -0
  244. package/packaging/conformance/fixtures/stop-goal-fit--block-bundle-disputed-claim.json +1 -1
  245. package/packaging/conformance/fixtures/stop-goal-fit--block-capture-contradicts-claimed-pass.json +1 -1
  246. package/packaging/manifest.json +9 -10
  247. package/schemas/assignment-provider-settings.schema.json +125 -0
  248. package/schemas/decision-record.schema.json +109 -0
  249. package/schemas/knowledge/edge.schema.json +54 -0
  250. package/schemas/knowledge/health-report.schema.json +45 -0
  251. package/schemas/knowledge/node.schema.json +49 -0
  252. package/schemas/knowledge/proposal.schema.json +53 -0
  253. package/schemas/workflow-evidence.schema.json +1 -1
  254. package/schemas/workflow-state.schema.json +5 -0
  255. package/scripts/README.md +8 -3
  256. package/scripts/check-content-boundary.cjs +1 -1
  257. package/scripts/check-decisions.cjs +356 -0
  258. package/scripts/ci/derive-claim-status.mjs +74 -0
  259. package/scripts/ci/trust-reconcile.js +957 -103
  260. package/scripts/git-status.sh +2 -2
  261. package/scripts/hooks/claude-telemetry-hook.js +37 -3
  262. package/scripts/hooks/codex-telemetry-hook.js +36 -2
  263. package/scripts/hooks/config-protection.js +102 -30
  264. package/scripts/hooks/evidence-capture.js +3 -3
  265. package/scripts/hooks/lib/actor-identity.js +368 -0
  266. package/scripts/hooks/lib/config-protection-remedies.js +69 -0
  267. package/scripts/hooks/lib/liveness-heartbeat.js +361 -0
  268. package/scripts/hooks/lib/liveness-policy.js +127 -0
  269. package/scripts/hooks/lib/liveness-read.js +105 -12
  270. package/scripts/hooks/lib/liveness-write.js +46 -0
  271. package/scripts/hooks/lib/local-artifact-paths.js +12 -6
  272. package/scripts/hooks/opencode-telemetry-hook.js +27 -1
  273. package/scripts/hooks/pi-telemetry-hook.js +27 -1
  274. package/scripts/hooks/stop-goal-fit.js +157 -26
  275. package/scripts/hooks/workflow-steering.js +77 -3
  276. package/scripts/install-codex-home.sh +138 -38
  277. package/scripts/statusline/flow-agents-statusline.js +2 -2
  278. package/src/cli/assignment-provider.ts +845 -0
  279. package/src/cli/console-learning-projection.ts +3 -2
  280. package/src/cli/effective-assignment-provider-settings.ts +112 -0
  281. package/src/cli/init.ts +101 -21
  282. package/src/cli/kit.ts +37 -6
  283. package/src/cli/public-api.test.mjs +58 -6
  284. package/src/cli/pull-work-provider.ts +1 -1
  285. package/src/cli/trust-bundle-policy-order.test.mjs +87 -0
  286. package/src/cli/usage-feedback.ts +3 -3
  287. package/src/cli/validate-hook-influence.ts +1 -0
  288. package/src/cli/validate-workflow-artifacts.ts +27 -7
  289. package/src/cli/veritas-readiness-adapter.test.mjs +267 -0
  290. package/src/cli/workflow-artifact-cleanup-audit.ts +35 -2
  291. package/src/cli/workflow-sidecar.ts +933 -168
  292. package/src/cli.ts +6 -0
  293. package/src/flow-kit/validate.ts +74 -1
  294. package/src/index.ts +6 -2
  295. package/src/lib/flow-resolver.ts +123 -55
  296. package/src/lib/local-artifact-root.ts +24 -5
  297. package/src/lib/workflow-learning-projection.ts +2 -2
  298. package/src/runtime-adapters.ts +61 -15
  299. package/src/tools/build-universal-bundles.ts +49 -16
  300. package/src/tools/generate-context-map.ts +10 -8
  301. package/src/tools/validate-source-tree.ts +24 -1
@@ -0,0 +1,153 @@
1
+ /**
2
+ * Provider-agnostic knowledge-health verbs.
3
+ *
4
+ * Each verb takes a graph ({ nodes, edges }) produced by ANY provider that
5
+ * implements the read side of the knowledge-store contract and returns a
6
+ * schema-valid Knowledge Health Report (schemas/knowledge/health-report.schema.json).
7
+ * The verbs never inspect provider internals — that is what lets the SAME health
8
+ * command run identically over the vault, git-repo, and work-item providers
9
+ * (AC1, AC2, R4).
10
+ *
11
+ * @module providers/health
12
+ */
13
+
14
+ import { loadSchemas } from "../lib/model.js";
15
+ import { assertValid } from "../lib/schema-validate.js";
16
+
17
+ const { healthReport: HEALTH_SCHEMA } = loadSchemas();
18
+
19
+ /** Normalise a title into a comparable token set. */
20
+ function tokenize(title) {
21
+ return new Set(
22
+ String(title || "")
23
+ .toLowerCase()
24
+ .replace(/[^a-z0-9\s]/g, " ")
25
+ .split(/\s+/)
26
+ .filter(Boolean),
27
+ );
28
+ }
29
+
30
+ function jaccard(a, b) {
31
+ if (a.size === 0 && b.size === 0) return 0;
32
+ let inter = 0;
33
+ for (const t of a) if (b.has(t)) inter++;
34
+ const union = a.size + b.size - inter;
35
+ return union === 0 ? 0 : inter / union;
36
+ }
37
+
38
+ function finalize(report) {
39
+ report.summary.finding_count = report.findings.length;
40
+ assertValid(report, HEALTH_SCHEMA, `health report (${report.check})`);
41
+ return report;
42
+ }
43
+
44
+ /**
45
+ * Duplicate detection: flag node PAIRS whose titles are the same or highly
46
+ * similar (token Jaccard >= threshold). Scoped to same-type pairs by default so
47
+ * a decision and an issue with a shared word are not conflated.
48
+ *
49
+ * @param {{nodes: object[], edges: object[]}} graph
50
+ * @param {object} [options]
51
+ * @param {string} [options.provider="unknown"]
52
+ * @param {number} [options.threshold=0.7]
53
+ * @param {boolean} [options.crossType=false] compare across node types when true
54
+ * @param {string} [options.generatedAt]
55
+ */
56
+ export function detectDuplicates(graph, options = {}) {
57
+ const nodes = graph.nodes || [];
58
+ const edges = graph.edges || [];
59
+ const threshold = typeof options.threshold === "number" ? options.threshold : 0.7;
60
+ const crossType = options.crossType === true;
61
+
62
+ const report = {
63
+ schema_version: "1.0",
64
+ check: "duplicate-detection",
65
+ provider: options.provider || "unknown",
66
+ generated_at: options.generatedAt || new Date().toISOString(),
67
+ summary: { nodes_examined: nodes.length, edges_examined: edges.length, finding_count: 0 },
68
+ findings: [],
69
+ };
70
+
71
+ const tokenized = nodes.map((n) => ({ node: n, tokens: tokenize(n.title) }));
72
+ for (let i = 0; i < tokenized.length; i++) {
73
+ for (let j = i + 1; j < tokenized.length; j++) {
74
+ const a = tokenized[i];
75
+ const b = tokenized[j];
76
+ if (!crossType && a.node.type !== b.node.type) continue;
77
+ const score = jaccard(a.tokens, b.tokens);
78
+ if (score >= threshold) {
79
+ report.findings.push({
80
+ kind: "duplicate-nodes",
81
+ severity: "warning",
82
+ node_ids: [a.node.id, b.node.id],
83
+ message: `Possible duplicate ${a.node.type} nodes: "${a.node.title}" ~ "${b.node.title}" (similarity ${score.toFixed(2)})`,
84
+ evidence: {
85
+ similarity: Number(score.toFixed(4)),
86
+ threshold,
87
+ title_a: a.node.title,
88
+ title_b: b.node.title,
89
+ type: a.node.type,
90
+ },
91
+ });
92
+ }
93
+ }
94
+ }
95
+ return finalize(report);
96
+ }
97
+
98
+ /**
99
+ * Dependency-link integrity: flag dependency edges whose endpoints do not
100
+ * resolve to a node present in the graph. An edge marked resolved:false is a
101
+ * deliberate external reference and is skipped.
102
+ *
103
+ * @param {{nodes: object[], edges: object[]}} graph
104
+ * @param {object} [options]
105
+ * @param {string} [options.provider="unknown"]
106
+ * @param {string[]} [options.dependencyTypes=["blocks"]] edge types treated as dependency links
107
+ * @param {string} [options.generatedAt]
108
+ */
109
+ export function checkDependencyLinkIntegrity(graph, options = {}) {
110
+ const nodes = graph.nodes || [];
111
+ const edges = graph.edges || [];
112
+ const depTypes = new Set(options.dependencyTypes || ["blocks"]);
113
+ const ids = new Set(nodes.map((n) => n.id));
114
+
115
+ const report = {
116
+ schema_version: "1.0",
117
+ check: "dependency-link-integrity",
118
+ provider: options.provider || "unknown",
119
+ generated_at: options.generatedAt || new Date().toISOString(),
120
+ summary: { nodes_examined: nodes.length, edges_examined: edges.length, finding_count: 0 },
121
+ findings: [],
122
+ };
123
+
124
+ for (const e of edges) {
125
+ if (!depTypes.has(e.type)) continue;
126
+ if (e.resolved === false) continue; // deliberate external ref
127
+ const sourceMissing = !ids.has(e.from);
128
+ const targetMissing = !ids.has(e.to);
129
+ if (!sourceMissing && !targetMissing) continue;
130
+ report.findings.push({
131
+ kind: "broken-dependency-link",
132
+ severity: "error",
133
+ edge_ids: [e.id],
134
+ node_ids: [e.from, e.to],
135
+ message: `Broken ${e.type} link ${e.from} -> ${e.to}: ${
136
+ targetMissing ? `target '${e.to}' not found` : ""
137
+ }${targetMissing && sourceMissing ? "; " : ""}${sourceMissing ? `source '${e.from}' not found` : ""}`.trim(),
138
+ evidence: {
139
+ edge_type: e.type,
140
+ from: e.from,
141
+ to: e.to,
142
+ source_missing: sourceMissing,
143
+ target_missing: targetMissing,
144
+ },
145
+ });
146
+ }
147
+ return finalize(report);
148
+ }
149
+
150
+ export const HEALTH_VERBS = Object.freeze({
151
+ "duplicate-detection": detectDuplicates,
152
+ "dependency-link-integrity": checkDependencyLinkIntegrity,
153
+ });
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Knowledge Kit store providers — public entry point.
3
+ *
4
+ * Typed graph model (nodes / edges / provenance) with pluggable providers behind
5
+ * one read + proposals-only write interface, plus provider-agnostic health verbs.
6
+ * See context/contracts/knowledge-store-contract.md.
7
+ *
8
+ * @module providers
9
+ */
10
+
11
+ export { MarkdownVaultProvider } from "./markdown-vault/index.js";
12
+ export { GitRepoProvider } from "./git-repo/index.js";
13
+ export { WorkItemProvider, parseWorkItemMetadata } from "./work-item/index.js";
14
+ export { detectDuplicates, checkDependencyLinkIntegrity, HEALTH_VERBS } from "./health/index.js";
15
+ export {
16
+ EDGE_TYPES,
17
+ CORE_NODE_TYPES,
18
+ loadSchemas,
19
+ node,
20
+ edge,
21
+ proposal,
22
+ provenance,
23
+ } from "./lib/model.js";
24
+ export { validate, assertValid } from "./lib/schema-validate.js";
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Knowledge graph model helpers: node / edge / proposal / provenance
3
+ * constructors, the closed edge-type vocabulary, the recommended node-type
4
+ * vocabulary, and loaders for the JSON schemas that define them.
5
+ *
6
+ * @module providers/lib/model
7
+ */
8
+
9
+ import * as fs from "node:fs";
10
+ import * as path from "node:path";
11
+ import { fileURLToPath } from "node:url";
12
+
13
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
14
+ const SCHEMA_DIR = path.resolve(__dirname, "../../../../schemas/knowledge");
15
+
16
+ /** Closed vocabulary of edge types (schema-enforced). */
17
+ export const EDGE_TYPES = Object.freeze([
18
+ "supersedes",
19
+ "merged-into",
20
+ "blocks",
21
+ "evidence-of",
22
+ "mentions",
23
+ "relates",
24
+ ]);
25
+
26
+ /** Recommended core node types. EXTENSIBLE — providers may emit others. */
27
+ export const CORE_NODE_TYPES = Object.freeze([
28
+ "note",
29
+ "decision",
30
+ "issue",
31
+ "session",
32
+ "person",
33
+ ]);
34
+
35
+ let _schemaCache = null;
36
+ /** Load and cache the four graph schemas. */
37
+ export function loadSchemas() {
38
+ if (_schemaCache) return _schemaCache;
39
+ const read = (name) => JSON.parse(fs.readFileSync(path.join(SCHEMA_DIR, name), "utf8"));
40
+ _schemaCache = {
41
+ node: read("node.schema.json"),
42
+ edge: read("edge.schema.json"),
43
+ proposal: read("proposal.schema.json"),
44
+ healthReport: read("health-report.schema.json"),
45
+ };
46
+ return _schemaCache;
47
+ }
48
+
49
+ /** Build a provenance object. */
50
+ export function provenance({ provider, source, locator, retrievedAt, agent }) {
51
+ const p = {
52
+ provider,
53
+ source,
54
+ retrieved_at: retrievedAt || new Date().toISOString(),
55
+ };
56
+ if (locator) p.locator = locator;
57
+ if (agent) p.agent = agent;
58
+ return p;
59
+ }
60
+
61
+ /** Build a node. Omits empty optional fields so schema additionalProperties holds. */
62
+ export function node({ id, type, title, body, attributes, provenance: prov }) {
63
+ const n = { id, type, title, provenance: prov };
64
+ if (body !== undefined) n.body = body;
65
+ if (attributes && Object.keys(attributes).length) n.attributes = attributes;
66
+ return n;
67
+ }
68
+
69
+ /** Build an edge. `resolved` defaults to true (an internal dependency link). */
70
+ export function edge({ id, type, from, to, resolved, attributes, provenance: prov }) {
71
+ const e = { id, type, from, to, provenance: prov };
72
+ if (resolved === false) e.resolved = false;
73
+ if (attributes && Object.keys(attributes).length) e.attributes = attributes;
74
+ return e;
75
+ }
76
+
77
+ /** Build a proposal. status is always "proposed" — proposals-only by construction. */
78
+ export function proposal({ provider, kind, target, payload, rendered, rationale, provenance: prov }) {
79
+ const p = {
80
+ schema_version: "1.0",
81
+ provider,
82
+ kind,
83
+ target: target || {},
84
+ status: "proposed",
85
+ provenance: prov,
86
+ };
87
+ if (payload) p.payload = payload;
88
+ if (rendered !== undefined) p.rendered = rendered;
89
+ if (rationale) p.rationale = rationale;
90
+ return p;
91
+ }
@@ -0,0 +1,119 @@
1
+ /**
2
+ * Minimal, dependency-free JSON-Schema validator.
3
+ *
4
+ * Supports exactly the draft-07 subset used by the Knowledge Kit graph schemas
5
+ * (schemas/knowledge/*.schema.json): type, required, properties,
6
+ * additionalProperties:false, enum, const, items, minLength, minItems, integer,
7
+ * and local `$ref` into `#/$defs/*`. It is intentionally NOT a general validator
8
+ * — it exists so health reports and conformance tests can assert schema validity
9
+ * without pulling an external dependency (the same zero-dep discipline the
10
+ * decision-registry validator follows).
11
+ *
12
+ * @module providers/lib/schema-validate
13
+ */
14
+
15
+ function typeOf(value) {
16
+ if (value === null) return "null";
17
+ if (Array.isArray(value)) return "array";
18
+ return typeof value;
19
+ }
20
+
21
+ function matchesType(value, type) {
22
+ if (type === "integer") return typeof value === "number" && Number.isInteger(value);
23
+ if (type === "number") return typeof value === "number";
24
+ if (type === "object") return typeOf(value) === "object";
25
+ if (type === "array") return Array.isArray(value);
26
+ return typeOf(value) === type;
27
+ }
28
+
29
+ function resolveRef(ref, root) {
30
+ if (!ref.startsWith("#/")) throw new Error(`Unsupported $ref (only local #/ refs): ${ref}`);
31
+ const parts = ref.slice(2).split("/");
32
+ let node = root;
33
+ for (const part of parts) {
34
+ node = node && node[part];
35
+ }
36
+ if (!node) throw new Error(`Unresolved $ref: ${ref}`);
37
+ return node;
38
+ }
39
+
40
+ function validateNode(value, schema, root, pathStr, errors) {
41
+ if (schema.$ref) {
42
+ return validateNode(value, resolveRef(schema.$ref, root), root, pathStr, errors);
43
+ }
44
+
45
+ if ("const" in schema) {
46
+ if (value !== schema.const) {
47
+ errors.push(`${pathStr}: expected const ${JSON.stringify(schema.const)}, got ${JSON.stringify(value)}`);
48
+ }
49
+ }
50
+
51
+ if (schema.enum) {
52
+ if (!schema.enum.includes(value)) {
53
+ errors.push(`${pathStr}: value ${JSON.stringify(value)} not in enum ${JSON.stringify(schema.enum)}`);
54
+ }
55
+ }
56
+
57
+ if (schema.type) {
58
+ const types = Array.isArray(schema.type) ? schema.type : [schema.type];
59
+ if (!types.some((t) => matchesType(value, t))) {
60
+ errors.push(`${pathStr}: expected type ${types.join("|")}, got ${typeOf(value)}`);
61
+ return; // further checks assume the type held
62
+ }
63
+ }
64
+
65
+ if (typeof value === "string") {
66
+ if (typeof schema.minLength === "number" && value.length < schema.minLength) {
67
+ errors.push(`${pathStr}: string shorter than minLength ${schema.minLength}`);
68
+ }
69
+ }
70
+
71
+ if (Array.isArray(value)) {
72
+ if (typeof schema.minItems === "number" && value.length < schema.minItems) {
73
+ errors.push(`${pathStr}: array shorter than minItems ${schema.minItems}`);
74
+ }
75
+ if (schema.items) {
76
+ value.forEach((item, i) => validateNode(item, schema.items, root, `${pathStr}[${i}]`, errors));
77
+ }
78
+ }
79
+
80
+ if (typeOf(value) === "object") {
81
+ const props = schema.properties || {};
82
+ for (const req of schema.required || []) {
83
+ if (!(req in value)) errors.push(`${pathStr}: missing required property '${req}'`);
84
+ }
85
+ if (schema.additionalProperties === false) {
86
+ for (const key of Object.keys(value)) {
87
+ if (!(key in props)) errors.push(`${pathStr}: unexpected property '${key}'`);
88
+ }
89
+ }
90
+ for (const [key, propSchema] of Object.entries(props)) {
91
+ if (key in value) {
92
+ validateNode(value[key], propSchema, root, `${pathStr}/${key}`, errors);
93
+ }
94
+ }
95
+ }
96
+ }
97
+
98
+ /**
99
+ * Validate `value` against `schema`.
100
+ * @returns {{ valid: boolean, errors: string[] }}
101
+ */
102
+ export function validate(value, schema) {
103
+ const errors = [];
104
+ validateNode(value, schema, schema, "$", errors);
105
+ return { valid: errors.length === 0, errors };
106
+ }
107
+
108
+ /**
109
+ * Assert validity, throwing a descriptive error when invalid. Handy in tests.
110
+ */
111
+ export function assertValid(value, schema, label = "value") {
112
+ const { valid, errors } = validate(value, schema);
113
+ if (!valid) {
114
+ const err = new Error(`${label} failed schema validation:\n ${errors.join("\n ")}`);
115
+ err.code = "SCHEMA_INVALID";
116
+ err.errors = errors;
117
+ throw err;
118
+ }
119
+ }
@@ -0,0 +1,169 @@
1
+ /**
2
+ * markdown-vault knowledge-store provider.
3
+ *
4
+ * Adapts the existing Knowledge Kit store (the markdown-vault behaviour:
5
+ * markdown files + YAML frontmatter + [[wikilinks]] + a JSON graph index) to
6
+ * the knowledge-store PROVIDER contract (typed nodes/edges/provenance). It is a
7
+ * thin READ + PROPOSE wrapper over an existing store adapter instance — it does
8
+ * NOT reimplement or modify the adapter, so existing Knowledge Kit skills are
9
+ * unaffected. Wikilinks/frontmatter remain the native form; proposeWrite renders
10
+ * a new note in exactly that form for a human to file.
11
+ *
12
+ * @module providers/markdown-vault
13
+ */
14
+
15
+ import { node, edge, proposal, provenance } from "../lib/model.js";
16
+ import DefaultKnowledgeStore from "../../adapters/default-store/index.js";
17
+
18
+ const PROVIDER_ID = "markdown-vault";
19
+
20
+ /** Vault record type -> graph node type. Extensible: unknowns fall back to note. */
21
+ function nodeTypeFor(recordType) {
22
+ return recordType === "person" ? "person" : "note";
23
+ }
24
+
25
+ /** Vault link kind -> closed graph edge type. */
26
+ function edgeTypeFor(linkKind) {
27
+ switch (linkKind) {
28
+ case "supersedes":
29
+ case "refines":
30
+ return "supersedes";
31
+ case "source":
32
+ case "example":
33
+ return "evidence-of";
34
+ case "appears-in":
35
+ case "person":
36
+ return "mentions";
37
+ case "proposes":
38
+ case "related":
39
+ default:
40
+ return "relates";
41
+ }
42
+ }
43
+
44
+ const ALL_RECORD_TYPES = ["raw", "compiled", "concept", "snapshot", "person"];
45
+
46
+ export class MarkdownVaultProvider {
47
+ constructor({ store, storeRoot, agent } = {}) {
48
+ if (!store && !storeRoot) {
49
+ throw new Error("MarkdownVaultProvider requires { store } or { storeRoot }");
50
+ }
51
+ this.store = store || new DefaultKnowledgeStore({ storeRoot });
52
+ this.storeRoot = storeRoot || "(injected store)";
53
+ this.agent = agent;
54
+ this.id = PROVIDER_ID;
55
+ }
56
+
57
+ capabilities() {
58
+ return {
59
+ id: PROVIDER_ID,
60
+ node_types: ["note", "person"],
61
+ edge_types: ["supersedes", "evidence-of", "mentions", "relates"],
62
+ writable: false,
63
+ write_mode: "proposals-only",
64
+ proposal_targets: ["create-node"],
65
+ source_of_truth: "human-curated markdown vault",
66
+ };
67
+ }
68
+
69
+ async _allRecords() {
70
+ const out = [];
71
+ for (const t of ALL_RECORD_TYPES) {
72
+ const recs = await this.store.listByType(t, { includeRetired: true });
73
+ for (const r of recs) out.push(r);
74
+ }
75
+ return out;
76
+ }
77
+
78
+ async readNodes(options = {}) {
79
+ const records = await this._allRecords();
80
+ const nodes = records.map((r) => {
81
+ const attributes = { record_type: r.type };
82
+ if (r.category) attributes.category = r.category;
83
+ if (r.status) attributes.status = r.status;
84
+ if (Array.isArray(r.tags) && r.tags.length) attributes.tags = r.tags;
85
+ return node({
86
+ id: r.id,
87
+ type: nodeTypeFor(r.type),
88
+ title: r.title,
89
+ body: typeof r.body === "string" ? r.body : "",
90
+ attributes,
91
+ provenance: provenance({
92
+ provider: PROVIDER_ID,
93
+ source: `${this.storeRoot}#${r.id}`,
94
+ locator: r.type,
95
+ agent: this.agent,
96
+ }),
97
+ });
98
+ });
99
+ if (options.type) return nodes.filter((n) => n.type === options.type);
100
+ return nodes;
101
+ }
102
+
103
+ async readEdges() {
104
+ const records = await this._allRecords();
105
+ const edges = [];
106
+ for (const r of records) {
107
+ const links = await this.store.getLinks(r.id);
108
+ for (const l of links.forward || []) {
109
+ edges.push(
110
+ edge({
111
+ id: `${r.id}--${l.kind}--${l.target_id}`,
112
+ type: edgeTypeFor(l.kind),
113
+ from: r.id,
114
+ to: l.target_id,
115
+ attributes: { vault_link_kind: l.kind },
116
+ provenance: provenance({
117
+ provider: PROVIDER_ID,
118
+ source: `${this.storeRoot}#${r.id}`,
119
+ locator: `links.${l.kind}`,
120
+ agent: this.agent,
121
+ }),
122
+ }),
123
+ );
124
+ }
125
+ }
126
+ return edges;
127
+ }
128
+
129
+ async queryByType(type) {
130
+ return this.readNodes({ type });
131
+ }
132
+
133
+ async readGraph() {
134
+ const [nodes, edges] = await Promise.all([this.readNodes(), this.readEdges()]);
135
+ return { nodes, edges };
136
+ }
137
+
138
+ /**
139
+ * Propose a new vault note. Renders the native markdown-vault form
140
+ * (frontmatter + [[wikilinks]]) WITHOUT touching the store — proposals only.
141
+ */
142
+ async proposeWrite(intent = {}) {
143
+ const { title, body = "", category = "inbox", tags = [], links = [] } = intent;
144
+ if (!title) throw new Error("markdown-vault proposeWrite requires intent.title");
145
+ const wikilinks = links.map((l) => `[[${l.target_id}${l.label ? `|${l.label}` : ""}]]`).join(" ");
146
+ const fm = [
147
+ "---",
148
+ "type: compiled",
149
+ `title: ${title}`,
150
+ `category: ${category}`,
151
+ tags.length ? `tags: [${tags.join(", ")}]` : "tags: []",
152
+ "---",
153
+ "",
154
+ body,
155
+ wikilinks ? `\n${wikilinks}` : "",
156
+ ].join("\n");
157
+ return proposal({
158
+ provider: PROVIDER_ID,
159
+ kind: "create-node",
160
+ target: { store_root: this.storeRoot, category },
161
+ payload: { type: "note", title, body, category, tags, links },
162
+ rendered: fm,
163
+ rationale: intent.rationale || "Proposed vault note (file it via the Knowledge Kit ingest/compile flow).",
164
+ provenance: provenance({ provider: PROVIDER_ID, source: this.storeRoot, agent: this.agent }),
165
+ });
166
+ }
167
+ }
168
+
169
+ export default MarkdownVaultProvider;