ai-collab-open-system 0.1.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 (259) hide show
  1. package/.aict/START_HERE.md +127 -0
  2. package/.aict/WORKSPACE_MANIFEST.json +91 -0
  3. package/.aict/acceptance/EXAMPLE.synthetic.md +49 -0
  4. package/.aict/acceptance/FAILURE_MODES.md +40 -0
  5. package/.aict/acceptance/PROMPT.md +47 -0
  6. package/.aict/acceptance/README.md +44 -0
  7. package/.aict/acceptance/TEMPLATE.md +57 -0
  8. package/.aict/adapters/SHARED_CORE_CONTRACT.md +106 -0
  9. package/.aict/adapters/claude-code/ADAPTER.md +28 -0
  10. package/.aict/adapters/cline/ADAPTER.md +28 -0
  11. package/.aict/adapters/codex/ADAPTER.md +28 -0
  12. package/.aict/adapters/copilot/ADAPTER.md +28 -0
  13. package/.aict/adapters/cursor/ADAPTER.md +28 -0
  14. package/.aict/adapters/windsurf/ADAPTER.md +28 -0
  15. package/.aict/context/EXAMPLE.synthetic.md +53 -0
  16. package/.aict/context/FAILURE_MODES.md +40 -0
  17. package/.aict/context/PROMPT.md +47 -0
  18. package/.aict/context/README.md +44 -0
  19. package/.aict/context/TEMPLATE.md +63 -0
  20. package/.aict/cookbook/README.md +8 -0
  21. package/.aict/cookbook/bridge-to-a-second-family.md +103 -0
  22. package/.aict/cookbook/connect-a-tool.md +67 -0
  23. package/.aict/cookbook/review-a-half-product.md +79 -0
  24. package/.aict/cookbook/run-a-first-loop.md +81 -0
  25. package/.aict/examples/README.md +21 -0
  26. package/.aict/examples/ai-coding-long-task/CASE.md +161 -0
  27. package/.aict/examples/ai-coding-long-task/artifacts/acceptance-card.md +36 -0
  28. package/.aict/examples/ai-coding-long-task/artifacts/context-package.md +30 -0
  29. package/.aict/examples/ai-coding-long-task/artifacts/execution-prompt.md +30 -0
  30. package/.aict/examples/ai-coding-long-task/artifacts/first-ai-output.md +109 -0
  31. package/.aict/examples/ai-coding-long-task/artifacts/guard-review.md +40 -0
  32. package/.aict/examples/ai-coding-long-task/artifacts/handoff-note.md +28 -0
  33. package/.aict/examples/ai-coding-long-task/artifacts/harvest-seed.md +28 -0
  34. package/.aict/examples/ai-coding-long-task/artifacts/revised-output.md +62 -0
  35. package/.aict/examples/content-production-harvest/CASE.md +87 -0
  36. package/.aict/examples/content-production-harvest/artifacts/acceptance-card.md +28 -0
  37. package/.aict/examples/content-production-harvest/artifacts/context-package.md +28 -0
  38. package/.aict/examples/content-production-harvest/artifacts/execution-prompt.md +30 -0
  39. package/.aict/examples/content-production-harvest/artifacts/guard-review.md +28 -0
  40. package/.aict/examples/content-production-harvest/artifacts/handoff-note.md +28 -0
  41. package/.aict/examples/content-production-harvest/artifacts/harvest-seed.md +28 -0
  42. package/.aict/examples/multi-tool-collaboration/CASE.md +87 -0
  43. package/.aict/examples/multi-tool-collaboration/artifacts/acceptance-card.md +28 -0
  44. package/.aict/examples/multi-tool-collaboration/artifacts/context-package.md +28 -0
  45. package/.aict/examples/multi-tool-collaboration/artifacts/execution-prompt.md +30 -0
  46. package/.aict/examples/multi-tool-collaboration/artifacts/guard-review.md +28 -0
  47. package/.aict/examples/multi-tool-collaboration/artifacts/handoff-note.md +28 -0
  48. package/.aict/examples/multi-tool-collaboration/artifacts/harvest-seed.md +28 -0
  49. package/.aict/examples/personal-judgment-growth-assistant/CASE.md +87 -0
  50. package/.aict/examples/personal-judgment-growth-assistant/artifacts/acceptance-card.md +28 -0
  51. package/.aict/examples/personal-judgment-growth-assistant/artifacts/context-package.md +28 -0
  52. package/.aict/examples/personal-judgment-growth-assistant/artifacts/execution-prompt.md +30 -0
  53. package/.aict/examples/personal-judgment-growth-assistant/artifacts/guard-review.md +28 -0
  54. package/.aict/examples/personal-judgment-growth-assistant/artifacts/handoff-note.md +28 -0
  55. package/.aict/examples/personal-judgment-growth-assistant/artifacts/harvest-seed.md +28 -0
  56. package/.aict/examples/research-knowledge-synthesis/CASE.md +87 -0
  57. package/.aict/examples/research-knowledge-synthesis/artifacts/acceptance-card.md +28 -0
  58. package/.aict/examples/research-knowledge-synthesis/artifacts/context-package.md +28 -0
  59. package/.aict/examples/research-knowledge-synthesis/artifacts/execution-prompt.md +30 -0
  60. package/.aict/examples/research-knowledge-synthesis/artifacts/guard-review.md +28 -0
  61. package/.aict/examples/research-knowledge-synthesis/artifacts/handoff-note.md +28 -0
  62. package/.aict/examples/research-knowledge-synthesis/artifacts/harvest-seed.md +28 -0
  63. package/.aict/guard/EXAMPLE.synthetic.md +51 -0
  64. package/.aict/guard/FAILURE_MODES.md +40 -0
  65. package/.aict/guard/PROMPT.md +47 -0
  66. package/.aict/guard/README.md +44 -0
  67. package/.aict/guard/TEMPLATE.md +60 -0
  68. package/.aict/handoff/EXAMPLE.synthetic.md +51 -0
  69. package/.aict/handoff/FAILURE_MODES.md +40 -0
  70. package/.aict/handoff/PROMPT.md +47 -0
  71. package/.aict/handoff/README.md +44 -0
  72. package/.aict/handoff/TEMPLATE.md +60 -0
  73. package/.aict/harvest/EXAMPLE.synthetic.md +51 -0
  74. package/.aict/harvest/FAILURE_MODES.md +40 -0
  75. package/.aict/harvest/PROMPT.md +47 -0
  76. package/.aict/harvest/README.md +44 -0
  77. package/.aict/harvest/TEMPLATE.md +60 -0
  78. package/.aict/mechanisms/README.md +34 -0
  79. package/.aict/mechanisms/anti-drift-partner/EXAMPLE.synthetic.md +46 -0
  80. package/.aict/mechanisms/anti-drift-partner/FAILURE_MODES.md +25 -0
  81. package/.aict/mechanisms/anti-drift-partner/PROMPT.md +75 -0
  82. package/.aict/mechanisms/anti-drift-partner/README.md +82 -0
  83. package/.aict/mechanisms/anti-drift-partner/TEMPLATE.md +74 -0
  84. package/.aict/mechanisms/blind-spot-scan/EXAMPLE.synthetic.md +39 -0
  85. package/.aict/mechanisms/blind-spot-scan/FAILURE_MODES.md +25 -0
  86. package/.aict/mechanisms/blind-spot-scan/PROMPT.md +72 -0
  87. package/.aict/mechanisms/blind-spot-scan/README.md +79 -0
  88. package/.aict/mechanisms/blind-spot-scan/TEMPLATE.md +70 -0
  89. package/.aict/mechanisms/collaboration-coach/EXAMPLE.synthetic.md +40 -0
  90. package/.aict/mechanisms/collaboration-coach/FAILURE_MODES.md +25 -0
  91. package/.aict/mechanisms/collaboration-coach/PROMPT.md +72 -0
  92. package/.aict/mechanisms/collaboration-coach/README.md +79 -0
  93. package/.aict/mechanisms/collaboration-coach/TEMPLATE.md +61 -0
  94. package/.aict/mechanisms/do-not-handle-yet/EXAMPLE.synthetic.md +15 -0
  95. package/.aict/mechanisms/do-not-handle-yet/FAILURE_MODES.md +16 -0
  96. package/.aict/mechanisms/do-not-handle-yet/PROMPT.md +41 -0
  97. package/.aict/mechanisms/do-not-handle-yet/README.md +30 -0
  98. package/.aict/mechanisms/do-not-handle-yet/TEMPLATE.md +38 -0
  99. package/.aict/mechanisms/dual-guard/EXAMPLE.synthetic.md +54 -0
  100. package/.aict/mechanisms/dual-guard/FAILURE_MODES.md +25 -0
  101. package/.aict/mechanisms/dual-guard/PROMPT.md +76 -0
  102. package/.aict/mechanisms/dual-guard/README.md +81 -0
  103. package/.aict/mechanisms/dual-guard/TEMPLATE.md +73 -0
  104. package/.aict/mechanisms/feedback-absorption-ledger/EXAMPLE.synthetic.md +49 -0
  105. package/.aict/mechanisms/feedback-absorption-ledger/FAILURE_MODES.md +25 -0
  106. package/.aict/mechanisms/feedback-absorption-ledger/PROMPT.md +74 -0
  107. package/.aict/mechanisms/feedback-absorption-ledger/README.md +81 -0
  108. package/.aict/mechanisms/feedback-absorption-ledger/TEMPLATE.md +69 -0
  109. package/.aict/mechanisms/half-product-review/EXAMPLE.synthetic.md +15 -0
  110. package/.aict/mechanisms/half-product-review/FAILURE_MODES.md +16 -0
  111. package/.aict/mechanisms/half-product-review/PROMPT.md +41 -0
  112. package/.aict/mechanisms/half-product-review/README.md +30 -0
  113. package/.aict/mechanisms/half-product-review/TEMPLATE.md +38 -0
  114. package/.aict/mechanisms/handoff-abc/EXAMPLE.synthetic.md +47 -0
  115. package/.aict/mechanisms/handoff-abc/FAILURE_MODES.md +25 -0
  116. package/.aict/mechanisms/handoff-abc/PROMPT.md +75 -0
  117. package/.aict/mechanisms/handoff-abc/README.md +82 -0
  118. package/.aict/mechanisms/handoff-abc/TEMPLATE.md +60 -0
  119. package/.aict/mechanisms/harvest-and-erc/EXAMPLE.synthetic.md +43 -0
  120. package/.aict/mechanisms/harvest-and-erc/FAILURE_MODES.md +25 -0
  121. package/.aict/mechanisms/harvest-and-erc/PROMPT.md +74 -0
  122. package/.aict/mechanisms/harvest-and-erc/README.md +81 -0
  123. package/.aict/mechanisms/harvest-and-erc/TEMPLATE.md +60 -0
  124. package/.aict/mechanisms/honest-calibration/EXAMPLE.synthetic.md +43 -0
  125. package/.aict/mechanisms/honest-calibration/FAILURE_MODES.md +25 -0
  126. package/.aict/mechanisms/honest-calibration/PROMPT.md +74 -0
  127. package/.aict/mechanisms/honest-calibration/README.md +81 -0
  128. package/.aict/mechanisms/honest-calibration/TEMPLATE.md +66 -0
  129. package/.aict/mechanisms/one-click-dispatch/EXAMPLE.synthetic.md +15 -0
  130. package/.aict/mechanisms/one-click-dispatch/FAILURE_MODES.md +16 -0
  131. package/.aict/mechanisms/one-click-dispatch/PROMPT.md +41 -0
  132. package/.aict/mechanisms/one-click-dispatch/README.md +30 -0
  133. package/.aict/mechanisms/one-click-dispatch/TEMPLATE.md +38 -0
  134. package/.aict/mechanisms/plain-language-first-screen/EXAMPLE.synthetic.md +15 -0
  135. package/.aict/mechanisms/plain-language-first-screen/FAILURE_MODES.md +16 -0
  136. package/.aict/mechanisms/plain-language-first-screen/PROMPT.md +41 -0
  137. package/.aict/mechanisms/plain-language-first-screen/README.md +30 -0
  138. package/.aict/mechanisms/plain-language-first-screen/TEMPLATE.md +38 -0
  139. package/.aict/mechanisms/root-cause-brake/EXAMPLE.synthetic.md +55 -0
  140. package/.aict/mechanisms/root-cause-brake/FAILURE_MODES.md +25 -0
  141. package/.aict/mechanisms/root-cause-brake/PROMPT.md +73 -0
  142. package/.aict/mechanisms/root-cause-brake/README.md +79 -0
  143. package/.aict/mechanisms/root-cause-brake/TEMPLATE.md +74 -0
  144. package/.aict/mechanisms/scout-review-controller/EXAMPLE.synthetic.md +15 -0
  145. package/.aict/mechanisms/scout-review-controller/FAILURE_MODES.md +16 -0
  146. package/.aict/mechanisms/scout-review-controller/PROMPT.md +41 -0
  147. package/.aict/mechanisms/scout-review-controller/README.md +30 -0
  148. package/.aict/mechanisms/scout-review-controller/TEMPLATE.md +38 -0
  149. package/.aict/mechanisms/single-tool-guard/EXAMPLE.synthetic.md +54 -0
  150. package/.aict/mechanisms/single-tool-guard/FAILURE_MODES.md +25 -0
  151. package/.aict/mechanisms/single-tool-guard/PROMPT.md +76 -0
  152. package/.aict/mechanisms/single-tool-guard/README.md +83 -0
  153. package/.aict/mechanisms/single-tool-guard/TEMPLATE.md +75 -0
  154. package/.aict/mechanisms/task-splitting/EXAMPLE.synthetic.md +53 -0
  155. package/.aict/mechanisms/task-splitting/FAILURE_MODES.md +25 -0
  156. package/.aict/mechanisms/task-splitting/PROMPT.md +72 -0
  157. package/.aict/mechanisms/task-splitting/README.md +79 -0
  158. package/.aict/mechanisms/task-splitting/TEMPLATE.md +76 -0
  159. package/.aict/modes/README.md +11 -0
  160. package/.aict/modes/execute.md +31 -0
  161. package/.aict/modes/handoff.md +29 -0
  162. package/.aict/modes/harvest.md +30 -0
  163. package/.aict/modes/review.md +28 -0
  164. package/.aict/modes/shape.md +34 -0
  165. package/.aict/privacy/COMMERCIAL_BOUNDARY.md +34 -0
  166. package/.aict/privacy/PRIVACY.md +36 -0
  167. package/.aict/privacy/REDACTION_CHECKLIST.md +12 -0
  168. package/.aict/profile/CANDIDATES.md +44 -0
  169. package/.aict/profile/EXAMPLE.synthetic.md +49 -0
  170. package/.aict/profile/FAILURE_MODES.md +40 -0
  171. package/.aict/profile/PROMPT.md +47 -0
  172. package/.aict/profile/README.md +44 -0
  173. package/.aict/profile/TEMPLATE.md +57 -0
  174. package/.aict/prompts/acceptance-definition.md +109 -0
  175. package/.aict/prompts/guard-review.md +116 -0
  176. package/.aict/prompts/handoff-generation.md +110 -0
  177. package/.aict/prompts/harvest-extraction.md +110 -0
  178. package/.aict/prompts/mode-switching.md +66 -0
  179. package/.aict/prompts/profile-creation.md +66 -0
  180. package/.aict/prompts/profile-refinement.md +66 -0
  181. package/.aict/prompts/project-context-packaging.md +113 -0
  182. package/.aict/prompts/red-team-challenge.md +106 -0
  183. package/.aict/prompts/rule-update-proposal.md +114 -0
  184. package/.aict/prompts/workflow-reset.md +109 -0
  185. package/.aict/roles/README.md +18 -0
  186. package/.aict/roles/executor.md +34 -0
  187. package/.aict/roles/harvester.md +33 -0
  188. package/.aict/roles/owner-controller.md +38 -0
  189. package/.aict/roles/scout.md +33 -0
  190. package/.aict/roles/supervisor.md +34 -0
  191. package/.aict/roles/system-guardian.md +34 -0
  192. package/.aict/skills/acceptance/SKILL.md +43 -0
  193. package/.aict/skills/context/SKILL.md +44 -0
  194. package/.aict/skills/evidence-pack/SKILL.md +42 -0
  195. package/.aict/skills/guard/SKILL.md +46 -0
  196. package/.aict/skills/handoff/SKILL.md +44 -0
  197. package/.aict/skills/harvest/SKILL.md +44 -0
  198. package/.aict/skills/mode-switch/SKILL.md +42 -0
  199. package/.aict/skills/profile/SKILL.md +42 -0
  200. package/.aict/skills/red-team/SKILL.md +42 -0
  201. package/.aict/skills/single-tool-guard/SKILL.md +42 -0
  202. package/.aict/state/CURRENT_STATE.md +13 -0
  203. package/.aict/state/DECISIONS.md +7 -0
  204. package/.aict/state/TASK_LOG.md +7 -0
  205. package/.aict/state/evidence.jsonl +2 -0
  206. package/.aict/state/learning-ledger.jsonl +1 -0
  207. package/.aict/state/receipts.jsonl +1 -0
  208. package/.aict/state/runs.jsonl +1 -0
  209. package/.aict/state/tasks.jsonl +1 -0
  210. package/.aict/walkthroughs/10-minute-your-task.md +107 -0
  211. package/.aict/walkthroughs/10-minute.md +43 -0
  212. package/.aict/walkthroughs/30-minute.md +22 -0
  213. package/.aict/walkthroughs/60-minute.md +27 -0
  214. package/.aict/walkthroughs/synthetic-loop-transcript.md +43 -0
  215. package/CHANGELOG.md +23 -0
  216. package/CODE_OF_CONDUCT.md +20 -0
  217. package/CONTRIBUTING.md +30 -0
  218. package/KNOWN_LIMITATIONS.md +54 -0
  219. package/LICENSE +199 -0
  220. package/PRODUCT_CONTRACT.md +446 -0
  221. package/README.md +245 -0
  222. package/RELEASE_CHECKLIST.md +78 -0
  223. package/SECURITY.md +56 -0
  224. package/START_HERE.md +89 -0
  225. package/bin/ai-collab.js +2 -0
  226. package/docs/DOGFOOD.md +85 -0
  227. package/docs/FEEDBACK.md +61 -0
  228. package/docs/FIRST_EXPERIENCE_SPEC.md +32 -0
  229. package/docs/FREE_VS_PAID.md +53 -0
  230. package/docs/PUBLIC_BOUNDARY.md +36 -0
  231. package/docs/PUBLIC_MAPPING.md +178 -0
  232. package/docs/RELEASE_PRIORITY.md +23 -0
  233. package/docs/WHY_THIS_EXISTS.md +36 -0
  234. package/docs/open-system/00-start-here.md +60 -0
  235. package/docs/open-system/01-ai-collaboration-os.md +33 -0
  236. package/docs/open-system/02-six-layer-architecture.md +45 -0
  237. package/docs/open-system/03-role-system.md +33 -0
  238. package/docs/open-system/04-core-mechanisms.md +34 -0
  239. package/docs/open-system/05-failure-patterns.md +31 -0
  240. package/docs/open-system/06-how-to-adapt-to-your-workflow.md +31 -0
  241. package/package.json +69 -0
  242. package/privacy-manifest.json +78 -0
  243. package/privacy-scan.local.json.example +18 -0
  244. package/scripts/lib/forbidden-in-pack.js +55 -0
  245. package/scripts/pack-check.js +154 -0
  246. package/scripts/privacy-scan.js +487 -0
  247. package/scripts/validate-contract.js +160 -0
  248. package/src/adapters.js +590 -0
  249. package/src/bootstrap.js +1184 -0
  250. package/src/catalog.js +2723 -0
  251. package/src/cli.js +2899 -0
  252. package/src/dialogue.js +470 -0
  253. package/src/i18n.js +1034 -0
  254. package/src/ledger.js +2011 -0
  255. package/src/render.js +1381 -0
  256. package/src/sendmodel.js +452 -0
  257. package/src/validate.js +1307 -0
  258. package/src/workspace.js +1679 -0
  259. package/tests/contract.test.js +8514 -0
@@ -0,0 +1,590 @@
1
+ import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
2
+ import path from "node:path";
3
+ import { renderSharedCoreContract } from "./render.js";
4
+ import { readLedger, confirmedProfileLearnings } from "./ledger.js";
5
+
6
+ const sixLayerProtocol = [
7
+ "Profile: capture stable collaboration preferences before long or recurring work.",
8
+ "Context: package the current task boundary, facts, assumptions, risks, and open questions.",
9
+ "Acceptance: define observable pass criteria before asking an AI tool to execute.",
10
+ "Guard: review artifacts against requirements, evidence, privacy, and scope before trust.",
11
+ "Handoff: leave the next session a concise state card with done, pending, blocked, and unverified work.",
12
+ "Harvest: extract reusable lessons, prompt fragments, and future rule candidates after a loop."
13
+ ];
14
+
15
+ // Single source of truth: the contract body embedded in every entrypoint is the
16
+ // exact text rendered into .aict/adapters/SHARED_CORE_CONTRACT.md by render.js.
17
+ // We inline it (instead of pointing at a file that `adapters install` does not
18
+ // create) so the rules — coaching layer, completion-claim guard routing
19
+ // (single-tool-guard / dual-guard / full fusion), restraint tiers, the first-run
20
+ // promise, and the six-layer core loop — are live the moment the tool reads its
21
+ // always-on instructions, with or without a generated workspace. Reusing
22
+ // renderSharedCoreContract() keeps this from drifting into a second copy.
23
+ function sharedCoreContractBody() {
24
+ return renderSharedCoreContract()
25
+ .replace(/^# Shared Core Contract\n+/, "")
26
+ .trimEnd();
27
+ }
28
+
29
+ // --- Confirmed-preference injection (the "white-filled profile" fix) ---------
30
+ //
31
+ // Filling a profile preference and confirming it used to only echo back in the
32
+ // tool's OWN `status` line — the rule files a real tool reads (CLAUDE.md,
33
+ // .cursorrules, AGENTS.md, ...) never carried it, so the AI actually doing the
34
+ // work could not see it. That made a confirmed preference feel adopted while
35
+ // being invisible to the assistant. This block closes that gap: at
36
+ // `adapters install` time we read the user's KEPT profile preferences straight
37
+ // from the learning ledger (the machine source of truth) and inline them into
38
+ // every generated rule file, so the assistant reads them as part of its
39
+ // always-on instructions.
40
+ //
41
+ // Honesty rules (the whole point of the proposed/confirmed buffer):
42
+ // - ONLY confirmed/edited rows are injected. A `proposed` row is an unreviewed
43
+ // guess; injecting it would pass an unconfirmed guess off as a standing rule.
44
+ // confirmedProfileLearnings() enforces this (it filters on the same
45
+ // isGraduatedLearningStatus the status recall uses).
46
+ // - When there are NONE, we do NOT fake one: the block says "No confirmed
47
+ // preferences yet" and points at how to add one, instead of a placeholder
48
+ // pseudo-preference.
49
+ // - The injected text is the ledger content VERBATIM (only outer whitespace is
50
+ // trimmed) — never paraphrased into a stronger or different instruction.
51
+ // - We read the ledger, never CANDIDATES.md, which self-declares it can drift
52
+ // from the ledger; the ledger is the source of truth.
53
+
54
+ // Resolve the workspace state dir for an adapters-install target. The target a
55
+ // user passes is normally their project root, whose workspace lives at
56
+ // `<target>/.aict` (with state under `.aict/state`); a user may also point
57
+ // `--target` straight at the `.aict` workspace dir itself. We mirror the CLI's
58
+ // own workspace detection (WORKSPACE_MANIFEST.json is the marker every generated
59
+ // `.aict` carries, and ONLY there) so this never drifts from where the run-layer
60
+ // commands read/write the ledger. We deliberately do NOT key off START_HERE.md:
61
+ // that doc also ships at the project root, so a bare-START_HERE.md probe matches
62
+ // the root and resolves state to <root>/state instead of <root>/.aict/state.
63
+ // Returns null when no real workspace is present — then there simply is no
64
+ // confirmed preference to inject (a fresh project that never ran `init`), and we
65
+ // say so rather than inventing one.
66
+ function resolveWorkspaceStateDir(targetRoot) {
67
+ const root = path.resolve(targetRoot);
68
+ if (existsSync(path.join(root, "WORKSPACE_MANIFEST.json"))) {
69
+ return path.join(root, "state");
70
+ }
71
+ if (existsSync(path.join(root, ".aict", "WORKSPACE_MANIFEST.json"))) {
72
+ return path.join(root, ".aict", "state");
73
+ }
74
+ return null;
75
+ }
76
+
77
+ // Read the user's KEPT profile preferences (confirmed/edited profile-type
78
+ // learning rows) for an adapters-install target, as the array of their `content`
79
+ // strings (trimmed, in ledger order). Safe-by-default: a target with no
80
+ // workspace, or a workspace whose learning ledger is missing/empty, yields []
81
+ // (no confirmed preferences — we will not fake any). A CORRUPT ledger is treated
82
+ // the same way (empty) instead of crashing the whole install: injecting the
83
+ // preference block is a courtesy on top of writing the rule files, and a broken
84
+ // ledger is surfaced by `validate`/`status`, not by refusing to lay down adapter
85
+ // guidance. Reuses readLedger + confirmedProfileLearnings so the read shape and
86
+ // the "what counts as kept" rule stay single-sourced with the rest of the CLI.
87
+ function readConfirmedPreferences(targetRoot) {
88
+ const stateDir = resolveWorkspaceStateDir(targetRoot);
89
+ if (!stateDir) return [];
90
+ let records;
91
+ try {
92
+ records = readLedger(stateDir, "learning");
93
+ } catch {
94
+ return []; // corrupt ledger: don't block the install; don't invent preferences
95
+ }
96
+ return confirmedProfileLearnings(records).map((row) => String(row.content).trim());
97
+ }
98
+
99
+ // Render the "Your confirmed preferences" section injected into every rule file.
100
+ // `preferences` is the array of confirmed/edited preference strings from
101
+ // readConfirmedPreferences(). With one or more, each is a verbatim bullet so the
102
+ // assistant reads exactly what the user kept. With NONE, the section honestly
103
+ // says so and shows how to add one — it never emits a placeholder preference, so
104
+ // a fresh workspace's rule files carry no fake standing rule.
105
+ function renderConfirmedPreferencesSection(preferences) {
106
+ const heading = "## Your confirmed preferences";
107
+ if (!Array.isArray(preferences) || preferences.length === 0) {
108
+ return `${heading}
109
+
110
+ No confirmed preferences yet. These are the standing collaboration preferences the user has reviewed and kept; once any exist they are listed here for you to follow. To add one: \`ai-collab learning add --type profile --content "..."\` then \`ai-collab learning confirm --id <id>\` (only confirmed/edited preferences appear here — an unreviewed guess never does).`;
111
+ }
112
+ const bullets = preferences.map((line) => `- ${line}`).join("\n");
113
+ return `${heading}
114
+
115
+ These are standing collaboration preferences the user has reviewed and confirmed. Treat them as always-on instructions for how to work with this user, alongside the shared core contract below:
116
+
117
+ ${bullets}`;
118
+ }
119
+
120
+ // Each entrypoint carries a short tool `key` (the value `--tool` selects on) plus
121
+ // the relative file it writes and a `detect` list: marker files/dirs whose
122
+ // presence in the target means that tool is already in use here. `--tool auto`
123
+ // installs only the entrypoints whose detect markers are found, so a fresh
124
+ // install does not silently scatter six instruction files into a project that
125
+ // only uses one tool. `--tool all` keeps the original behavior (write all six).
126
+ //
127
+ // Detection-marker rule: a `detect` marker must be SPECIFIC to its tool, never a
128
+ // generic dir most repos already have. The cautionary case is Copilot: its
129
+ // marker is `.github/copilot-instructions.md` (the tool's own file), NOT the bare
130
+ // `.github/` dir — nearly every GitHub repo has `.github/` for workflows/issue
131
+ // templates, so detecting on the directory would auto-"find" Copilot everywhere
132
+ // and pollute unrelated repos. The bias is intentional: prefer "this tool's
133
+ // user was not auto-detected, so tell them to pass --tool" over "a plain repo
134
+ // got a wall of files it never asked for."
135
+ const adapterEntrypoints = [
136
+ {
137
+ key: "codex",
138
+ relativePath: "AGENTS.md",
139
+ tool: "Codex / AGENTS.md",
140
+ // AGENTS.md is now a vendor-neutral agent-instruction standard (Codex is its
141
+ // origin/primary consumer, but Cursor, Copilot, Jules, etc. also read it).
142
+ // It is still a precise "an AI agent is configured here" signal — unlike a
143
+ // bare framework dir, a repo only has AGENTS.md if someone added agent
144
+ // instructions — so it is a safe auto marker; we just install the Codex
145
+ // entrypoint (which writes AGENTS.md) for it. Users wanting a different tool
146
+ // can always pass --tool explicitly.
147
+ detect: ["AGENTS.md"]
148
+ },
149
+ {
150
+ key: "claude",
151
+ relativePath: "CLAUDE.md",
152
+ tool: "Claude Code / CLAUDE.md",
153
+ detect: ["CLAUDE.md", ".claude"]
154
+ },
155
+ {
156
+ key: "cursor",
157
+ relativePath: ".cursor/rules/ai-collab.mdc",
158
+ tool: "Cursor rules",
159
+ detect: [".cursor", ".cursorrules"]
160
+ },
161
+ {
162
+ key: "copilot",
163
+ relativePath: ".github/copilot-instructions.md",
164
+ tool: "GitHub Copilot instructions",
165
+ // Detect Copilot by its OWN instruction file, never by the bare `.github/`
166
+ // directory: almost every GitHub repo has `.github/` (workflows, issue
167
+ // templates, CODEOWNERS), so detecting on the directory would make the
168
+ // default `--tool auto` "find" Copilot in nearly every repo and write
169
+ // `.github/copilot-instructions.md` into projects that do not use Copilot.
170
+ detect: [".github/copilot-instructions.md"]
171
+ },
172
+ {
173
+ key: "cline",
174
+ relativePath: ".clinerules",
175
+ tool: "Cline rules",
176
+ detect: [".clinerules"]
177
+ },
178
+ {
179
+ key: "windsurf",
180
+ relativePath: ".windsurf/rules/ai-collab.md",
181
+ tool: "Windsurf rules",
182
+ detect: [".windsurf"]
183
+ }
184
+ ];
185
+
186
+ export const adapterToolKeys = adapterEntrypoints.map((entry) => entry.key);
187
+
188
+ // Parse the --tool value into a concrete set of tool keys (or the symbolic
189
+ // "all" / "auto" selectors). Accepts a comma-separated list, validates every
190
+ // token, and throws on an unknown tool so a typo fails loudly instead of
191
+ // silently installing nothing.
192
+ export function parseToolSelection(rawValue) {
193
+ const value = (rawValue ?? "auto").trim();
194
+ if (value === "") return { mode: "auto", keys: [] };
195
+ const tokens = value
196
+ .split(",")
197
+ .map((token) => token.trim().toLowerCase())
198
+ .filter((token) => token.length > 0);
199
+ if (tokens.length === 0) return { mode: "auto", keys: [] };
200
+
201
+ if (tokens.includes("all")) return { mode: "all", keys: [...adapterToolKeys] };
202
+ if (tokens.includes("auto")) {
203
+ if (tokens.length > 1) {
204
+ throw new Error(`--tool auto cannot be combined with other tools (got: ${value}).`);
205
+ }
206
+ return { mode: "auto", keys: [] };
207
+ }
208
+
209
+ const known = new Set(adapterToolKeys);
210
+ const selected = [];
211
+ for (const token of tokens) {
212
+ if (!known.has(token)) {
213
+ throw new Error(`Unknown --tool "${token}". Valid: ${adapterToolKeys.join(", ")}, all, auto.`);
214
+ }
215
+ if (!selected.includes(token)) selected.push(token);
216
+ }
217
+ return { mode: "explicit", keys: selected };
218
+ }
219
+
220
+ // Auto-detection: a tool is "present" when any of its marker files/dirs exists
221
+ // in the target. Returns the matched tool keys (in canonical order).
222
+ export function detectTools(targetRoot) {
223
+ const root = path.resolve(targetRoot);
224
+ return adapterEntrypoints
225
+ .filter((entry) => entry.detect.some((marker) => existsSync(path.join(root, marker))))
226
+ .map((entry) => entry.key);
227
+ }
228
+
229
+ function ensureDir(dir) {
230
+ mkdirSync(dir, { recursive: true });
231
+ }
232
+
233
+ function writeText(file, content) {
234
+ ensureDir(path.dirname(file));
235
+ writeFileSync(file, `${content.trimEnd()}\n`, "utf8");
236
+ }
237
+
238
+ function formatTimestamp(date = new Date()) {
239
+ const pad = (value) => String(value).padStart(2, "0");
240
+ return [
241
+ date.getFullYear(),
242
+ pad(date.getMonth() + 1),
243
+ pad(date.getDate())
244
+ ].join("") + "-" + [pad(date.getHours()), pad(date.getMinutes()), pad(date.getSeconds())].join("");
245
+ }
246
+
247
+ function backupPathFor(file) {
248
+ const base = `${file}.aict-backup-${formatTimestamp()}`;
249
+ if (!existsSync(base)) return base;
250
+ for (let index = 2; index < 100; index += 1) {
251
+ const candidate = `${base}-${index}`;
252
+ if (!existsSync(candidate)) return candidate;
253
+ }
254
+ throw new Error(`Could not choose a backup path for ${file}.`);
255
+ }
256
+
257
+ function renderAdapterEntrypoint(entry, preferences = []) {
258
+ return `# AI Collaboration Open System Adapter Guidance
259
+
260
+ This file is adapter guidance for ${entry.tool}. It is not a deep integration, background agent, telemetry hook, or hosted memory service.
261
+
262
+ This file is self-contained: the full shared core contract is embedded below, so the rules are live as soon as the tool reads its always-on instructions — you do not need to open any other file first. If you also ran \`node bin/ai-collab.js init --target <dir>\` (after the package is published to npm: \`ai-collab init --target <dir>\`), the deeper layer templates and examples live in that local \`.aict/\` workspace (\`.aict/profile\`, \`.aict/context\`, \`.aict/acceptance\`, \`.aict/guard\`, \`.aict/handoff\`, \`.aict/harvest\`, and \`.aict/mechanisms\`); this contract is the same one written to \`.aict/adapters/SHARED_CORE_CONTRACT.md\`.
263
+
264
+ ${renderConfirmedPreferencesSection(preferences)}
265
+
266
+ ## Six-layer core protocol
267
+
268
+ ${sixLayerProtocol.map((line) => `- ${line}`).join("\n")}
269
+
270
+ ## Adaptation tiers (least to most invasive)
271
+
272
+ - Rules (this file): the default, always-safe tier. Just guidance the tool reads; no automation.
273
+ - Skills (optional): reusable ability cards under \`.aict/skills/\` you load into a tool on demand after running \`init\`.
274
+ - Hooks (opt-in, off by default): \`adapters install --enable-hooks\` can add ONE project-local Claude Code Stop hook that reminds you to capture a receipt when you claim a task is done. It is never a global hook, the install lists every file first, and it is removable.
275
+
276
+ ## Minimal operating rule
277
+
278
+ 1. Follow the shared core contract embedded below as the single rule source for every tool — do not maintain a separate, drifting rule set.
279
+ 2. Keep private material local. Do not upload files or infer hidden memory.
280
+ 3. Label facts, assumptions, decisions, open questions, and unverified claims.
281
+ 4. Before claiming completion, cite the acceptance card or say what remains unverified.
282
+
283
+ ---
284
+
285
+ ${sharedCoreContractBody()}
286
+
287
+ ---
288
+
289
+ ## Honesty boundary
290
+
291
+ This adapter guidance only gives the tool a shared workflow. It does not synchronize memory across tools, automate every AI client, or guarantee better output without user review.
292
+ `;
293
+ }
294
+
295
+ // --- Optional hook layer (the "spearhead" demo; OFF by default) -------------
296
+ //
297
+ // This is the third, most invasive adaptation tier, layered ABOVE the always-safe
298
+ // rules entrypoints and the opt-in skills. It is gated behind an explicit
299
+ // --enable-hooks: the SECURITY / PRODUCT_CONTRACT promise is "no hooks without
300
+ // consent and never a global hook", so installAdapters() never touches this tier
301
+ // unless the caller turns it on. When on, it merges ONE Claude Code project-LOCAL
302
+ // Stop hook into the target's own .claude/settings.json (never the user's
303
+ // home/global config) that reminds the assistant to capture evidence + run
304
+ // `ai-collab receipt create` when it claims a task is done — the run-layer's
305
+ // completion checkpoint, surfaced at the exact moment a completion claim happens.
306
+ //
307
+ // Design: the reminder is INLINED into the settings command (a single self-
308
+ // contained `printf ... 1>&2; exit 0`) rather than shelling out to a separate
309
+ // script in the standard hooks directory. That keeps the install to one file, and
310
+ // — deliberately — avoids ever writing the standard `<claude-dir>/hooks` path
311
+ // literal, which the project's own privacy scanner flags as a leaked personal
312
+ // hook config. So a user who installs this hook and then runs the privacy scan on
313
+ // their own project still passes; the generated settings reference only the local
314
+ // dir name, not the hooks subpath.
315
+ const CLAUDE_LOCAL_DIR = ".claude";
316
+ // A stable marker tagged onto the Stop hook entry so it can be found and removed
317
+ // again (the "uninstallable" guarantee) without disturbing any other hooks the
318
+ // project already configured.
319
+ const HOOK_MARKER = "ai-collab-receipt-reminder";
320
+
321
+ function hookSettingsRelPosix() {
322
+ return [CLAUDE_LOCAL_DIR, "settings.json"].join("/");
323
+ }
324
+
325
+ // The inlined Stop-hook command. It is intentionally tiny and side-effect-free:
326
+ // it only prints guidance to stderr (which Claude Code surfaces from a Stop hook)
327
+ // and exits 0, so enabling it can never block the assistant from stopping or
328
+ // mutate any file. No absolute paths, tokens, script files, or private material —
329
+ // it is fully generic and self-contained.
330
+ function hookCommandString() {
331
+ const lines = [
332
+ "[ai-collab] Claimed a task is done? Capture evidence + a receipt:",
333
+ " ai-collab evidence add --task <id> --kind output --summary <text>",
334
+ " ai-collab receipt create --task <id> --verdict <pass|reject|insufficient_evidence|pass_with_risk> --guard-level <L0-L4>"
335
+ ];
336
+ return `printf '%s\\n' ${lines.map((line) => `'${line}'`).join(" ")} 1>&2; exit 0`;
337
+ }
338
+
339
+ function hookEntry() {
340
+ return {
341
+ // Tag the matcher with our marker so the entry is identifiable + removable.
342
+ matcher: HOOK_MARKER,
343
+ hooks: [
344
+ {
345
+ type: "command",
346
+ command: hookCommandString(),
347
+ timeout: 5000,
348
+ statusMessage: "ai-collab: remind to capture evidence + receipt"
349
+ }
350
+ ]
351
+ };
352
+ }
353
+
354
+ // True if a parsed settings object already has our marked Stop hook, so a repeat
355
+ // --enable-hooks is idempotent (we do not append a duplicate entry).
356
+ function hasOurStopHook(settings) {
357
+ const stop = settings?.hooks?.Stop;
358
+ if (!Array.isArray(stop)) return false;
359
+ return stop.some((entry) => entry && entry.matcher === HOOK_MARKER);
360
+ }
361
+
362
+ // Merge our Stop hook into an existing settings object WITHOUT dropping anything
363
+ // the project already configured: other events, other Stop entries, and all other
364
+ // keys are preserved; we only append our marked entry to hooks.Stop.
365
+ function mergeStopHook(settings) {
366
+ const next = settings && typeof settings === "object" ? { ...settings } : {};
367
+ const hooks = next.hooks && typeof next.hooks === "object" ? { ...next.hooks } : {};
368
+ const stop = Array.isArray(hooks.Stop) ? [...hooks.Stop] : [];
369
+ stop.push(hookEntry());
370
+ hooks.Stop = stop;
371
+ next.hooks = hooks;
372
+ return next;
373
+ }
374
+
375
+ // Plan the hook-layer writes for the target. Hooks are Claude-Code-specific, so
376
+ // they only apply when the claude entrypoint is in the selected set. Returns a
377
+ // list of planned file actions (create / merge / backup-replace / skip) plus a
378
+ // reason when nothing is planned, so the CLI can explain exactly what (if
379
+ // anything) --enable-hooks will do — including in --dry-run.
380
+ export function plannedHookActions(targetRoot, selectedKeys) {
381
+ const root = path.resolve(targetRoot);
382
+ const keys = new Set(selectedKeys ?? []);
383
+ if (!keys.has("claude")) {
384
+ return { applicable: false, reason: "hooks are Claude Code only; select --tool claude (or a set including it) to enable them", actions: [] };
385
+ }
386
+
387
+ const actions = [];
388
+ const settingsFull = path.join(root, CLAUDE_LOCAL_DIR, "settings.json");
389
+ let settingsAction = "create";
390
+ if (existsSync(settingsFull)) {
391
+ let parsed = null;
392
+ try {
393
+ parsed = JSON.parse(readFileSync(settingsFull, "utf8"));
394
+ } catch {
395
+ parsed = undefined; // unparseable -> we will not clobber it; surface as a skip
396
+ }
397
+ if (parsed === undefined) {
398
+ settingsAction = "skip-unparseable";
399
+ } else if (hasOurStopHook(parsed)) {
400
+ settingsAction = "already-present";
401
+ } else {
402
+ settingsAction = "merge";
403
+ }
404
+ }
405
+ actions.push({
406
+ kind: "hook-settings",
407
+ relativePath: hookSettingsRelPosix(),
408
+ path: settingsFull,
409
+ action: settingsAction
410
+ });
411
+
412
+ return { applicable: true, reason: null, actions };
413
+ }
414
+
415
+ // Execute the planned hook writes. Mirrors installAdapters' safety posture:
416
+ // existing files are backed up before replacement, an unparseable settings.json
417
+ // is left untouched (reported, not clobbered), and a settings.json that already
418
+ // carries our marked entry is left as-is (idempotent).
419
+ function applyHookActions(targetRoot, plan, { dryRun }) {
420
+ const root = path.resolve(targetRoot);
421
+ const written = [];
422
+ const backups = [];
423
+
424
+ if (dryRun || !plan.applicable) {
425
+ return { written, backups };
426
+ }
427
+
428
+ for (const item of plan.actions) {
429
+ if (item.kind === "hook-settings") {
430
+ if (item.action === "skip-unparseable" || item.action === "already-present") {
431
+ continue; // never clobber an unparseable file; never duplicate our entry
432
+ }
433
+ if (item.action === "create") {
434
+ ensureDir(path.dirname(item.path));
435
+ writeFileSync(item.path, `${JSON.stringify(mergeStopHook({}), null, 2)}\n`, "utf8");
436
+ written.push(item.relativePath);
437
+ } else if (item.action === "merge") {
438
+ const existing = JSON.parse(readFileSync(item.path, "utf8"));
439
+ const backup = backupPathFor(item.path);
440
+ renameSync(item.path, backup);
441
+ backups.push(backup);
442
+ writeFileSync(item.path, `${JSON.stringify(mergeStopHook(existing), null, 2)}\n`, "utf8");
443
+ written.push(item.relativePath);
444
+ }
445
+ }
446
+ }
447
+
448
+ return { written, backups };
449
+ }
450
+
451
+ // Resolve which entrypoints to install. `selectedKeys` is the concrete tool-key
452
+ // list (already expanded from --tool by parseToolSelection / detectTools).
453
+ // Each returned entry tags its `action`: "create" (no existing file) or
454
+ // "backup-replace" (a file already exists and would be backed up first), so the
455
+ // CLI can list exactly which paths are created vs replaced — including in
456
+ // --dry-run, where nothing is actually written.
457
+ export function plannedAdapterEntrypoints(target, selectedKeys) {
458
+ const targetRoot = path.resolve(target);
459
+ const keys = selectedKeys ?? adapterToolKeys;
460
+ const keySet = new Set(keys);
461
+ // Read the user's confirmed/edited profile preferences ONCE (not per file) so
462
+ // every rule file we generate carries the same kept-preference block. When the
463
+ // target has no workspace / no kept preference, this is [] and the block
464
+ // honestly says "No confirmed preferences yet" (see renderConfirmedPreferencesSection).
465
+ const preferences = readConfirmedPreferences(targetRoot);
466
+ return adapterEntrypoints
467
+ .filter((entry) => keySet.has(entry.key))
468
+ .map((entry) => {
469
+ const fullPath = path.join(targetRoot, entry.relativePath);
470
+ return {
471
+ key: entry.key,
472
+ tool: entry.tool,
473
+ relativePath: entry.relativePath,
474
+ path: fullPath,
475
+ action: existsSync(fullPath) ? "backup-replace" : "create",
476
+ content: renderAdapterEntrypoint(entry, preferences)
477
+ };
478
+ });
479
+ }
480
+
481
+ // Decide the effective tool key set from the --tool option. `auto` (the default)
482
+ // resolves to the detected tools; when nothing is detected it returns an empty
483
+ // set plus `autoFoundNothing: true` so the caller can prompt the user to pass an
484
+ // explicit --tool instead of silently scattering all six files.
485
+ export function resolveToolSelection(targetRoot, toolOption) {
486
+ const selection = parseToolSelection(toolOption);
487
+ if (selection.mode === "auto") {
488
+ const detected = detectTools(targetRoot);
489
+ return { mode: "auto", keys: detected, detected, autoFoundNothing: detected.length === 0 };
490
+ }
491
+ return { mode: selection.mode, keys: selection.keys, detected: null, autoFoundNothing: false };
492
+ }
493
+
494
+ export function installAdapters(target, options = {}) {
495
+ const targetRoot = path.resolve(target);
496
+ const resolution = resolveToolSelection(targetRoot, options.tool);
497
+ const entries = plannedAdapterEntrypoints(targetRoot, resolution.keys);
498
+
499
+ // Opt-in hook layer (OFF unless --enable-hooks). Plan it here so the same plan
500
+ // is reported in dry-run and executed in a real run; nothing is planned when
501
+ // the flag is off, so the rules/skills tiers are entirely unaffected.
502
+ const hookPlan = options.enableHooks
503
+ ? plannedHookActions(targetRoot, resolution.keys)
504
+ : { applicable: false, reason: "hooks not requested (pass --enable-hooks to opt in)", actions: [] };
505
+
506
+ // Auto mode with no tool detected: do not write anything. Tell the caller to
507
+ // pick a tool explicitly (or `--tool all`). This is surfaced (not thrown) so
508
+ // both dry-run and real install report it cleanly.
509
+ if (resolution.autoFoundNothing) {
510
+ return {
511
+ targetRoot,
512
+ files: 0,
513
+ dryRun: Boolean(options.dryRun),
514
+ written: false,
515
+ backups: [],
516
+ toolMode: resolution.mode,
517
+ detected: resolution.detected,
518
+ autoFoundNothing: true,
519
+ plan: [],
520
+ hooks: { enabled: Boolean(options.enableHooks), applicable: false, reason: hookPlan.reason, plan: [], written: [] }
521
+ };
522
+ }
523
+
524
+ const plan = entries.map((entry) => ({
525
+ tool: entry.tool,
526
+ relativePath: entry.relativePath,
527
+ path: entry.path,
528
+ action: entry.action
529
+ }));
530
+
531
+ if (options.dryRun) {
532
+ return {
533
+ targetRoot,
534
+ files: entries.length,
535
+ dryRun: true,
536
+ written: false,
537
+ backups: [],
538
+ toolMode: resolution.mode,
539
+ detected: resolution.detected,
540
+ autoFoundNothing: false,
541
+ plan,
542
+ hooks: {
543
+ enabled: Boolean(options.enableHooks),
544
+ applicable: hookPlan.applicable,
545
+ reason: hookPlan.reason,
546
+ plan: hookPlan.actions.map((item) => ({ relativePath: item.relativePath, action: item.action })),
547
+ written: []
548
+ }
549
+ };
550
+ }
551
+
552
+ const collisions = entries.filter((entry) => existsSync(entry.path));
553
+ if (collisions.length > 0 && !options.force) {
554
+ throw new Error(
555
+ `Adapter file already exists: ${collisions[0].relativePath}. Pass --force to back up and replace adapter guidance.`
556
+ );
557
+ }
558
+
559
+ const backups = [];
560
+ for (const entry of entries) {
561
+ if (existsSync(entry.path)) {
562
+ const backup = backupPathFor(entry.path);
563
+ renameSync(entry.path, backup);
564
+ backups.push(backup);
565
+ }
566
+ writeText(entry.path, entry.content);
567
+ }
568
+
569
+ const hookResult = applyHookActions(targetRoot, hookPlan, { dryRun: false });
570
+ backups.push(...hookResult.backups);
571
+
572
+ return {
573
+ targetRoot,
574
+ files: entries.length,
575
+ dryRun: false,
576
+ written: true,
577
+ backups,
578
+ toolMode: resolution.mode,
579
+ detected: resolution.detected,
580
+ autoFoundNothing: false,
581
+ plan,
582
+ hooks: {
583
+ enabled: Boolean(options.enableHooks),
584
+ applicable: hookPlan.applicable,
585
+ reason: hookPlan.reason,
586
+ plan: hookPlan.actions.map((item) => ({ relativePath: item.relativePath, action: item.action })),
587
+ written: hookResult.written
588
+ }
589
+ };
590
+ }