specweave 1.0.579 → 1.0.581

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 (115) hide show
  1. package/dist/dashboard/assets/{index-C434W7yF.js → index-Dpn4T2Mx.js} +7 -7
  2. package/dist/dashboard/index.html +1 -1
  3. package/dist/src/core/hooks/LifecycleHookDispatcher.d.ts +6 -1
  4. package/dist/src/core/hooks/LifecycleHookDispatcher.d.ts.map +1 -1
  5. package/dist/src/core/hooks/LifecycleHookDispatcher.js +66 -8
  6. package/dist/src/core/hooks/LifecycleHookDispatcher.js.map +1 -1
  7. package/dist/src/core/increment/completion-validator.d.ts +18 -0
  8. package/dist/src/core/increment/completion-validator.d.ts.map +1 -1
  9. package/dist/src/core/increment/completion-validator.js +75 -0
  10. package/dist/src/core/increment/completion-validator.js.map +1 -1
  11. package/dist/src/core/increment/status-commands.d.ts.map +1 -1
  12. package/dist/src/core/increment/status-commands.js +16 -1
  13. package/dist/src/core/increment/status-commands.js.map +1 -1
  14. package/dist/src/core/reflect-nudge/aggregator.d.ts +33 -0
  15. package/dist/src/core/reflect-nudge/aggregator.d.ts.map +1 -0
  16. package/dist/src/core/reflect-nudge/aggregator.js +80 -0
  17. package/dist/src/core/reflect-nudge/aggregator.js.map +1 -0
  18. package/dist/src/core/reflect-nudge/eligibility.d.ts +27 -0
  19. package/dist/src/core/reflect-nudge/eligibility.d.ts.map +1 -0
  20. package/dist/src/core/reflect-nudge/eligibility.js +114 -0
  21. package/dist/src/core/reflect-nudge/eligibility.js.map +1 -0
  22. package/dist/src/core/reflect-nudge/index.d.ts +14 -0
  23. package/dist/src/core/reflect-nudge/index.d.ts.map +1 -0
  24. package/dist/src/core/reflect-nudge/index.js +14 -0
  25. package/dist/src/core/reflect-nudge/index.js.map +1 -0
  26. package/dist/src/core/reflect-nudge/interactive-prompt.d.ts +28 -0
  27. package/dist/src/core/reflect-nudge/interactive-prompt.d.ts.map +1 -0
  28. package/dist/src/core/reflect-nudge/interactive-prompt.js +52 -0
  29. package/dist/src/core/reflect-nudge/interactive-prompt.js.map +1 -0
  30. package/dist/src/core/reflect-nudge/prompt-renderer.d.ts +23 -0
  31. package/dist/src/core/reflect-nudge/prompt-renderer.d.ts.map +1 -0
  32. package/dist/src/core/reflect-nudge/prompt-renderer.js +26 -0
  33. package/dist/src/core/reflect-nudge/prompt-renderer.js.map +1 -0
  34. package/dist/src/core/reflect-nudge/session-end-nudge.d.ts +31 -0
  35. package/dist/src/core/reflect-nudge/session-end-nudge.d.ts.map +1 -0
  36. package/dist/src/core/reflect-nudge/session-end-nudge.js +44 -0
  37. package/dist/src/core/reflect-nudge/session-end-nudge.js.map +1 -0
  38. package/dist/src/core/reflection/reflect-handler.d.ts +2 -0
  39. package/dist/src/core/reflection/reflect-handler.d.ts.map +1 -1
  40. package/dist/src/core/reflection/reflect-handler.js +4 -0
  41. package/dist/src/core/reflection/reflect-handler.js.map +1 -1
  42. package/dist/src/core/rubric/rubric-evaluator.d.ts +15 -0
  43. package/dist/src/core/rubric/rubric-evaluator.d.ts.map +1 -1
  44. package/dist/src/core/rubric/rubric-evaluator.js +59 -0
  45. package/dist/src/core/rubric/rubric-evaluator.js.map +1 -1
  46. package/dist/src/core/skill-attribution.d.ts +78 -0
  47. package/dist/src/core/skill-attribution.d.ts.map +1 -0
  48. package/dist/src/core/skill-attribution.js +168 -0
  49. package/dist/src/core/skill-attribution.js.map +1 -0
  50. package/dist/src/core/skill-refine/aggregator.d.ts +36 -0
  51. package/dist/src/core/skill-refine/aggregator.d.ts.map +1 -0
  52. package/dist/src/core/skill-refine/aggregator.js +58 -0
  53. package/dist/src/core/skill-refine/aggregator.js.map +1 -0
  54. package/dist/src/core/skill-refine/apply.d.ts +48 -0
  55. package/dist/src/core/skill-refine/apply.d.ts.map +1 -0
  56. package/dist/src/core/skill-refine/apply.js +97 -0
  57. package/dist/src/core/skill-refine/apply.js.map +1 -0
  58. package/dist/src/core/skill-refine/approval.d.ts +71 -0
  59. package/dist/src/core/skill-refine/approval.d.ts.map +1 -0
  60. package/dist/src/core/skill-refine/approval.js +113 -0
  61. package/dist/src/core/skill-refine/approval.js.map +1 -0
  62. package/dist/src/core/skill-refine/haiku-diff.d.ts +66 -0
  63. package/dist/src/core/skill-refine/haiku-diff.d.ts.map +1 -0
  64. package/dist/src/core/skill-refine/haiku-diff.js +111 -0
  65. package/dist/src/core/skill-refine/haiku-diff.js.map +1 -0
  66. package/dist/src/core/skill-refine/ledger.d.ts +34 -0
  67. package/dist/src/core/skill-refine/ledger.d.ts.map +1 -0
  68. package/dist/src/core/skill-refine/ledger.js +81 -0
  69. package/dist/src/core/skill-refine/ledger.js.map +1 -0
  70. package/dist/src/core/skill-signal-emit.d.ts +46 -0
  71. package/dist/src/core/skill-signal-emit.d.ts.map +1 -0
  72. package/dist/src/core/skill-signal-emit.js +59 -0
  73. package/dist/src/core/skill-signal-emit.js.map +1 -0
  74. package/dist/src/core/skill-signals/index.d.ts +9 -0
  75. package/dist/src/core/skill-signals/index.d.ts.map +1 -0
  76. package/dist/src/core/skill-signals/index.js +9 -0
  77. package/dist/src/core/skill-signals/index.js.map +1 -0
  78. package/dist/src/core/skill-signals/reader.d.ts +30 -0
  79. package/dist/src/core/skill-signals/reader.d.ts.map +1 -0
  80. package/dist/src/core/skill-signals/reader.js +102 -0
  81. package/dist/src/core/skill-signals/reader.js.map +1 -0
  82. package/dist/src/core/skill-signals/writer.d.ts +34 -0
  83. package/dist/src/core/skill-signals/writer.d.ts.map +1 -0
  84. package/dist/src/core/skill-signals/writer.js +62 -0
  85. package/dist/src/core/skill-signals/writer.js.map +1 -0
  86. package/dist/src/core/skills/skill-judge.d.ts +8 -0
  87. package/dist/src/core/skills/skill-judge.d.ts.map +1 -1
  88. package/dist/src/core/skills/skill-judge.js +55 -0
  89. package/dist/src/core/skills/skill-judge.js.map +1 -1
  90. package/dist/src/skills/reflect-status.d.ts +23 -0
  91. package/dist/src/skills/reflect-status.d.ts.map +1 -0
  92. package/dist/src/skills/reflect-status.js +55 -0
  93. package/dist/src/skills/reflect-status.js.map +1 -0
  94. package/dist/src/skills/skill-refine.d.ts +81 -0
  95. package/dist/src/skills/skill-refine.d.ts.map +1 -0
  96. package/dist/src/skills/skill-refine.js +226 -0
  97. package/dist/src/skills/skill-refine.js.map +1 -0
  98. package/dist/src/sync/sync-coordinator.d.ts +14 -4
  99. package/dist/src/sync/sync-coordinator.d.ts.map +1 -1
  100. package/dist/src/sync/sync-coordinator.js +100 -38
  101. package/dist/src/sync/sync-coordinator.js.map +1 -1
  102. package/dist/src/types/skill-refinements.d.ts +75 -0
  103. package/dist/src/types/skill-refinements.d.ts.map +1 -0
  104. package/dist/src/types/skill-refinements.js +43 -0
  105. package/dist/src/types/skill-refinements.js.map +1 -0
  106. package/dist/src/types/skill-signals.d.ts +200 -0
  107. package/dist/src/types/skill-signals.d.ts.map +1 -0
  108. package/dist/src/types/skill-signals.js +115 -0
  109. package/dist/src/types/skill-signals.js.map +1 -0
  110. package/package.json +2 -2
  111. package/plugins/specweave/commands/reflect.md +48 -0
  112. package/plugins/specweave/skills/code-reviewer/SKILL.md +4 -0
  113. package/plugins/specweave/skills/judge-llm/SKILL.md +4 -0
  114. package/plugins/specweave/skills/skill-gen/SKILL.md +8 -0
  115. package/plugins/specweave/skills/skill-refine/SKILL.md +96 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"apply.d.ts","sourceRoot":"","sources":["../../../../src/core/skill-refine/apply.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAKH,OAAO,EAAE,OAAO,EAAkB,MAAM,MAAM,CAAC;AAC/C,OAAO,EAEL,KAAK,UAAU,EAChB,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAAiB,KAAK,qBAAqB,EAAE,MAAM,UAAU,CAAC;AAErE,MAAM,WAAW,YAAY;IAC3B,mDAAmD;IACnD,SAAS,EAAE,MAAM,CAAC;IAClB,gDAAgD;IAChD,IAAI,EAAE,MAAM,CAAC;IACb,qEAAqE;IACrE,SAAS,EAAE,MAAM,CAAC;IAClB,yEAAyE;IACzE,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,iFAAiF;IACjF,WAAW,EAAE,MAAM,CAAC;IACpB,kEAAkE;IAClE,UAAU,EAAE,MAAM,CAAC;IACnB,4DAA4D;IAC5D,QAAQ,EAAE,MAAM,CAAC;IACjB,8EAA8E;IAC9E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,sEAAsE;IACtE,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,UAAU,CAAC,CAAC;CAC/D;AAED,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,qBAAqB,CAAC;CACpC;AAED;;;GAGG;AACH,wBAAsB,eAAe,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,WAAW,CAAC,CAkE9E;AA0BD,OAAO,EAAE,OAAO,EAAE,CAAC"}
@@ -0,0 +1,97 @@
1
+ /**
2
+ * Apply an approved diff to a SKILL.md file, commit the change, and record
3
+ * the event in the ledger.
4
+ *
5
+ * AC-US2-04: writes the diff AND runs `git commit -m "refine(<skill>): <rationale>"`.
6
+ * NEVER pushes.
7
+ * AC-US2-05: records the refinement in `.specweave/state/skill-refinements.json`.
8
+ *
9
+ * Git operations go through `execFileNoThrow` so arguments cannot be shell-
10
+ * interpolated. If any step fails the SKILL.md is restored from the pre-diff
11
+ * snapshot and no ledger entry is written.
12
+ *
13
+ * @module core/skill-refine/apply
14
+ */
15
+ import { promises as fs } from 'fs';
16
+ import { mkdtemp, writeFile, unlink } from 'fs/promises';
17
+ import { tmpdir } from 'os';
18
+ import { dirname, join, relative } from 'path';
19
+ import { execFileNoThrow, } from '../../utils/execFileNoThrow';
20
+ import { appendApplied } from './ledger';
21
+ /**
22
+ * Apply the diff + commit + record. Throws on any step failure; callers
23
+ * translate the error into user-facing text.
24
+ */
25
+ export async function applyRefinement(opts) {
26
+ if (!opts.diff.trim()) {
27
+ throw new Error('applyRefinement: diff is empty — nothing to apply.');
28
+ }
29
+ const runGit = opts.runGit ?? ((args, cwd) => execFileNoThrow('git', args, { cwd }));
30
+ // Snapshot SKILL.md for rollback.
31
+ const snapshot = await fs.readFile(opts.skillPath, 'utf-8').catch(() => '');
32
+ const hadFile = snapshot.length > 0;
33
+ const patchPath = await writePatchTempfile(opts.diff);
34
+ try {
35
+ // Apply relative to the repo root so --- a/... / +++ b/... paths resolve.
36
+ const apply = await runGit(['apply', '--whitespace=nowarn', patchPath], opts.repoRoot);
37
+ if (!apply.success) {
38
+ throw new Error(`git apply failed: ${apply.stderr || apply.stdout || 'unknown'}`);
39
+ }
40
+ // Stage only the target SKILL.md — never blanket-stage the repo.
41
+ const relSkill = relative(opts.repoRoot, opts.skillPath);
42
+ const add = await runGit(['add', '--', relSkill], opts.repoRoot);
43
+ if (!add.success) {
44
+ throw new Error(`git add failed: ${add.stderr || 'unknown'}`);
45
+ }
46
+ const commit = await runGit([
47
+ 'commit',
48
+ '-m',
49
+ `refine(${opts.targetSkill}): ${truncateFirstLine(opts.rationale)}`,
50
+ ], opts.repoRoot);
51
+ if (!commit.success) {
52
+ throw new Error(`git commit failed: ${commit.stderr || commit.stdout || 'unknown'}`);
53
+ }
54
+ const revParse = await runGit(['rev-parse', 'HEAD'], opts.repoRoot);
55
+ const commitSha = (revParse.stdout || '').trim() || 'unknown';
56
+ const author = opts.author ?? (await resolveGitAuthor(opts.repoRoot, runGit));
57
+ const ledgerEntry = await appendApplied(opts.ledgerPath, {
58
+ targetSkill: opts.targetSkill,
59
+ author,
60
+ signalIds: opts.signalIds,
61
+ diffSha: commitSha,
62
+ rationale: opts.rationale,
63
+ });
64
+ return { commitSha, ledgerEntry };
65
+ }
66
+ catch (err) {
67
+ // Restore pre-apply snapshot — never leave SKILL.md in a half-applied state.
68
+ if (hadFile) {
69
+ await fs.writeFile(opts.skillPath, snapshot, 'utf-8').catch(() => undefined);
70
+ }
71
+ throw err;
72
+ }
73
+ finally {
74
+ await unlink(patchPath).catch(() => undefined);
75
+ }
76
+ }
77
+ async function writePatchTempfile(diff) {
78
+ const dir = await mkdtemp(join(tmpdir(), 'skill-refine-patch-'));
79
+ const path = join(dir, 'diff.patch');
80
+ // Ensure trailing newline — git apply is picky otherwise.
81
+ const body = diff.endsWith('\n') ? diff : diff + '\n';
82
+ await writeFile(path, body, 'utf-8');
83
+ return path;
84
+ }
85
+ async function resolveGitAuthor(repoRoot, runGit) {
86
+ const res = await runGit(['config', 'user.email'], repoRoot);
87
+ if (res.success && res.stdout.trim())
88
+ return res.stdout.trim();
89
+ return 'unknown';
90
+ }
91
+ function truncateFirstLine(text, max = 72) {
92
+ const firstLine = text.split('\n')[0] ?? '';
93
+ return firstLine.length <= max ? firstLine : firstLine.slice(0, max - 1) + '…';
94
+ }
95
+ // helper for apply.ts consumers (re-exported for convenience)
96
+ export { dirname };
97
+ //# sourceMappingURL=apply.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"apply.js","sourceRoot":"","sources":["../../../../src/core/skill-refine/apply.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,QAAQ,IAAI,EAAE,EAAE,MAAM,IAAI,CAAC;AACpC,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACzD,OAAO,EAAE,MAAM,EAAE,MAAM,IAAI,CAAC;AAC5B,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,MAAM,CAAC;AAC/C,OAAO,EACL,eAAe,GAEhB,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAAE,aAAa,EAA8B,MAAM,UAAU,CAAC;AA4BrE;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,IAAkB;IACtD,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC;QACtB,MAAM,IAAI,KAAK,CAAC,oDAAoD,CAAC,CAAC;IACxE,CAAC;IAED,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,CAAC,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE,CAAC,eAAe,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC;IAErF,kCAAkC;IAClC,MAAM,QAAQ,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC;IAC5E,MAAM,OAAO,GAAG,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC;IAEpC,MAAM,SAAS,GAAG,MAAM,kBAAkB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAEtD,IAAI,CAAC;QACH,0EAA0E;QAC1E,MAAM,KAAK,GAAG,MAAM,MAAM,CACxB,CAAC,OAAO,EAAE,qBAAqB,EAAE,SAAS,CAAC,EAC3C,IAAI,CAAC,QAAQ,CACd,CAAC;QACF,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC;YACnB,MAAM,IAAI,KAAK,CAAC,qBAAqB,KAAK,CAAC,MAAM,IAAI,KAAK,CAAC,MAAM,IAAI,SAAS,EAAE,CAAC,CAAC;QACpF,CAAC;QAED,iEAAiE;QACjE,MAAM,QAAQ,GAAG,QAAQ,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;QACzD,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,CAAC,KAAK,EAAE,IAAI,EAAE,QAAQ,CAAC,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;QACjE,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC;YACjB,MAAM,IAAI,KAAK,CAAC,mBAAmB,GAAG,CAAC,MAAM,IAAI,SAAS,EAAE,CAAC,CAAC;QAChE,CAAC;QAED,MAAM,MAAM,GAAG,MAAM,MAAM,CACzB;YACE,QAAQ;YACR,IAAI;YACJ,UAAU,IAAI,CAAC,WAAW,MAAM,iBAAiB,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE;SACpE,EACD,IAAI,CAAC,QAAQ,CACd,CAAC;QACF,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YACpB,MAAM,IAAI,KAAK,CAAC,sBAAsB,MAAM,CAAC,MAAM,IAAI,MAAM,CAAC,MAAM,IAAI,SAAS,EAAE,CAAC,CAAC;QACvF,CAAC;QAED,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,CAAC,WAAW,EAAE,MAAM,CAAC,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;QACpE,MAAM,SAAS,GAAG,CAAC,QAAQ,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,SAAS,CAAC;QAE9D,MAAM,MAAM,GACV,IAAI,CAAC,MAAM,IAAI,CAAC,MAAM,gBAAgB,CAAC,IAAI,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC;QAEjE,MAAM,WAAW,GAAG,MAAM,aAAa,CAAC,IAAI,CAAC,UAAU,EAAE;YACvD,WAAW,EAAE,IAAI,CAAC,WAAW;YAC7B,MAAM;YACN,SAAS,EAAE,IAAI,CAAC,SAAS;YACzB,OAAO,EAAE,SAAS;YAClB,SAAS,EAAE,IAAI,CAAC,SAAS;SAC1B,CAAC,CAAC;QAEH,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,CAAC;IACpC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,6EAA6E;QAC7E,IAAI,OAAO,EAAE,CAAC;YACZ,MAAM,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,SAAS,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;QAC/E,CAAC;QACD,MAAM,GAAG,CAAC;IACZ,CAAC;YAAS,CAAC;QACT,MAAM,MAAM,CAAC,SAAS,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;IACjD,CAAC;AACH,CAAC;AAED,KAAK,UAAU,kBAAkB,CAAC,IAAY;IAC5C,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,qBAAqB,CAAC,CAAC,CAAC;IACjE,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,EAAE,YAAY,CAAC,CAAC;IACrC,0DAA0D;IAC1D,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,GAAG,IAAI,CAAC;IACtD,MAAM,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC;IACrC,OAAO,IAAI,CAAC;AACd,CAAC;AAED,KAAK,UAAU,gBAAgB,CAC7B,QAAgB,EAChB,MAA4D;IAE5D,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,CAAC,QAAQ,EAAE,YAAY,CAAC,EAAE,QAAQ,CAAC,CAAC;IAC7D,IAAI,GAAG,CAAC,OAAO,IAAI,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE;QAAE,OAAO,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;IAC/D,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,SAAS,iBAAiB,CAAC,IAAY,EAAE,GAAG,GAAG,EAAE;IAC/C,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IAC5C,OAAO,SAAS,CAAC,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC;AACjF,CAAC;AAED,8DAA8D;AAC9D,OAAO,EAAE,OAAO,EAAE,CAAC"}
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Interactive approve/reject/edit prompt for `sw:skill-refine`.
3
+ *
4
+ * The user is shown the proposed diff + rationale and chooses one of:
5
+ * - **approve**: the caller applies the diff verbatim.
6
+ * - **reject**: the caller records a rejection (with reason) in the ledger;
7
+ * no SKILL.md write happens.
8
+ * - **edit**: the user's `$EDITOR` opens on the diff so they can hand-tune
9
+ * it before re-prompting for approval. Rejection during edit path is also
10
+ * honored.
11
+ *
12
+ * The `promptFns` param lets tests inject deterministic behavior; in
13
+ * production we default to `@inquirer/prompts` (already used elsewhere in
14
+ * specweave for wizard UI).
15
+ *
16
+ * @module core/skill-refine/approval
17
+ */
18
+ export type Decision = {
19
+ action: 'approve';
20
+ diff: string;
21
+ } | {
22
+ action: 'reject';
23
+ reason: string;
24
+ } | {
25
+ action: 'edit';
26
+ diff: string;
27
+ };
28
+ export interface ApprovalPromptContext {
29
+ targetSkill: string;
30
+ rationale: string;
31
+ diff: string;
32
+ }
33
+ export interface PromptFns {
34
+ /** Presents the approve/reject/edit menu. */
35
+ chooseAction: (ctx: ApprovalPromptContext) => Promise<'approve' | 'reject' | 'edit'>;
36
+ /** Prompted only when user picked 'reject'. */
37
+ askRejectionReason: () => Promise<string>;
38
+ /** Prompted only when user picked 'edit'; returns the new diff text. */
39
+ editDiff: (original: string) => Promise<string>;
40
+ }
41
+ export interface ApprovalOptions {
42
+ /** Max edit→re-ask loops before forcing a final decision. Default 3. */
43
+ maxEditLoops?: number;
44
+ }
45
+ /**
46
+ * Drive the approval flow. Returns a terminal decision:
47
+ * `{action:'approve', diff}` (the final, possibly-edited diff) OR
48
+ * `{action:'reject', reason}`.
49
+ *
50
+ * If the user loops through edits more than `maxEditLoops` without approving,
51
+ * the loop terminates as a rejection with an auto-reason so we never hang.
52
+ */
53
+ export declare function runApprovalFlow(ctx: ApprovalPromptContext, prompts: PromptFns, opts?: ApprovalOptions): Promise<{
54
+ action: 'approve';
55
+ diff: string;
56
+ } | {
57
+ action: 'reject';
58
+ reason: string;
59
+ }>;
60
+ /**
61
+ * Default production prompts backed by `@inquirer/prompts` + `$EDITOR`.
62
+ * Lazy-imported so tests that use stubs don't pay the dep cost.
63
+ */
64
+ export declare function defaultPromptFns(): Promise<PromptFns>;
65
+ /**
66
+ * Write `text` to a temp file, launch `$EDITOR` (or `vi` fallback), read the
67
+ * file back. Exposed for tests that want to validate the fallback path
68
+ * without actually spawning an editor.
69
+ */
70
+ export declare function openInEditor(text: string): string;
71
+ //# sourceMappingURL=approval.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"approval.d.ts","sourceRoot":"","sources":["../../../../src/core/skill-refine/approval.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAOH,MAAM,MAAM,QAAQ,GAChB;IAAE,MAAM,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACnC;IAAE,MAAM,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACpC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAErC,MAAM,WAAW,qBAAqB;IACpC,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,SAAS;IACxB,6CAA6C;IAC7C,YAAY,EAAE,CAAC,GAAG,EAAE,qBAAqB,KAAK,OAAO,CAAC,SAAS,GAAG,QAAQ,GAAG,MAAM,CAAC,CAAC;IACrF,+CAA+C;IAC/C,kBAAkB,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;IAC1C,wEAAwE;IACxE,QAAQ,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;CACjD;AAED,MAAM,WAAW,eAAe;IAC9B,wEAAwE;IACxE,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAID;;;;;;;GAOG;AACH,wBAAsB,eAAe,CACnC,GAAG,EAAE,qBAAqB,EAC1B,OAAO,EAAE,SAAS,EAClB,IAAI,GAAE,eAAoB,GACzB,OAAO,CAAC;IAAE,MAAM,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,MAAM,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CA8BrF;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,SAAS,CAAC,CA4B3D;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAejD"}
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Interactive approve/reject/edit prompt for `sw:skill-refine`.
3
+ *
4
+ * The user is shown the proposed diff + rationale and chooses one of:
5
+ * - **approve**: the caller applies the diff verbatim.
6
+ * - **reject**: the caller records a rejection (with reason) in the ledger;
7
+ * no SKILL.md write happens.
8
+ * - **edit**: the user's `$EDITOR` opens on the diff so they can hand-tune
9
+ * it before re-prompting for approval. Rejection during edit path is also
10
+ * honored.
11
+ *
12
+ * The `promptFns` param lets tests inject deterministic behavior; in
13
+ * production we default to `@inquirer/prompts` (already used elsewhere in
14
+ * specweave for wizard UI).
15
+ *
16
+ * @module core/skill-refine/approval
17
+ */
18
+ import { mkdtempSync, readFileSync, writeFileSync, rmSync } from 'fs';
19
+ import { tmpdir } from 'os';
20
+ import { join } from 'path';
21
+ import { spawnSync } from 'child_process';
22
+ const DEFAULT_MAX_EDIT_LOOPS = 3;
23
+ /**
24
+ * Drive the approval flow. Returns a terminal decision:
25
+ * `{action:'approve', diff}` (the final, possibly-edited diff) OR
26
+ * `{action:'reject', reason}`.
27
+ *
28
+ * If the user loops through edits more than `maxEditLoops` without approving,
29
+ * the loop terminates as a rejection with an auto-reason so we never hang.
30
+ */
31
+ export async function runApprovalFlow(ctx, prompts, opts = {}) {
32
+ const maxLoops = opts.maxEditLoops ?? DEFAULT_MAX_EDIT_LOOPS;
33
+ let currentDiff = ctx.diff;
34
+ let edits = 0;
35
+ while (true) {
36
+ const action = await prompts.chooseAction({
37
+ targetSkill: ctx.targetSkill,
38
+ rationale: ctx.rationale,
39
+ diff: currentDiff,
40
+ });
41
+ if (action === 'approve') {
42
+ return { action: 'approve', diff: currentDiff };
43
+ }
44
+ if (action === 'reject') {
45
+ const reason = await prompts.askRejectionReason();
46
+ return { action: 'reject', reason: reason || 'user rejected diff' };
47
+ }
48
+ // edit path
49
+ if (edits >= maxLoops) {
50
+ return {
51
+ action: 'reject',
52
+ reason: `auto-rejected: edit limit (${maxLoops}) exceeded without approval`,
53
+ };
54
+ }
55
+ currentDiff = await prompts.editDiff(currentDiff);
56
+ edits += 1;
57
+ }
58
+ }
59
+ /**
60
+ * Default production prompts backed by `@inquirer/prompts` + `$EDITOR`.
61
+ * Lazy-imported so tests that use stubs don't pay the dep cost.
62
+ */
63
+ export async function defaultPromptFns() {
64
+ const { select, input } = await import('@inquirer/prompts');
65
+ return {
66
+ chooseAction: async (ctx) => {
67
+ // eslint-disable-next-line no-console
68
+ console.log(`\n── sw:skill-refine — ${ctx.targetSkill} ──`);
69
+ // eslint-disable-next-line no-console
70
+ console.log(`rationale: ${ctx.rationale}\n`);
71
+ // eslint-disable-next-line no-console
72
+ console.log(ctx.diff);
73
+ return (await select({
74
+ message: 'Apply this diff?',
75
+ choices: [
76
+ { name: 'approve — write + commit', value: 'approve' },
77
+ { name: 'reject — discard', value: 'reject' },
78
+ { name: 'edit — open $EDITOR on the diff', value: 'edit' },
79
+ ],
80
+ }));
81
+ },
82
+ askRejectionReason: async () => {
83
+ return input({
84
+ message: 'Reason for rejection (recorded in ledger):',
85
+ default: 'not suitable',
86
+ });
87
+ },
88
+ editDiff: async (original) => openInEditor(original),
89
+ };
90
+ }
91
+ /**
92
+ * Write `text` to a temp file, launch `$EDITOR` (or `vi` fallback), read the
93
+ * file back. Exposed for tests that want to validate the fallback path
94
+ * without actually spawning an editor.
95
+ */
96
+ export function openInEditor(text) {
97
+ const editor = process.env.EDITOR || process.env.VISUAL || 'vi';
98
+ const dir = mkdtempSync(join(tmpdir(), 'skill-refine-edit-'));
99
+ const path = join(dir, 'diff.patch');
100
+ try {
101
+ writeFileSync(path, text, 'utf-8');
102
+ const result = spawnSync(editor, [path], { stdio: 'inherit' });
103
+ if (result.status !== 0) {
104
+ // Editor closed uncleanly — keep original to avoid surprise data loss.
105
+ return text;
106
+ }
107
+ return readFileSync(path, 'utf-8');
108
+ }
109
+ finally {
110
+ rmSync(dir, { recursive: true, force: true });
111
+ }
112
+ }
113
+ //# sourceMappingURL=approval.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"approval.js","sourceRoot":"","sources":["../../../../src/core/skill-refine/approval.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Haiku-backed diff proposer for `sw:skill-refine`.
3
+ *
4
+ * Given the current SKILL.md contents and an aggregate of refinement signals,
5
+ * asks Claude Haiku for a unified-diff proposal and a short rationale. The
6
+ * model is invoked at `temperature: 0` so repeat runs with the same inputs
7
+ * yield byte-identical output (AC-US2-02 determinism check).
8
+ *
9
+ * The Anthropic client is injected via the `client` option so tests can stub
10
+ * the call — production callers pass the real SDK instance.
11
+ *
12
+ * @module core/skill-refine/haiku-diff
13
+ */
14
+ import type { AggregateResult } from './aggregator';
15
+ export declare const HAIKU_MODEL = "claude-haiku-4-5-20251001";
16
+ export interface HaikuDiffResult {
17
+ diff: string;
18
+ rationale: string;
19
+ }
20
+ export interface HaikuClientLike {
21
+ messages: {
22
+ create: (args: {
23
+ model: string;
24
+ max_tokens: number;
25
+ temperature: number;
26
+ system?: string;
27
+ messages: Array<{
28
+ role: 'user' | 'assistant';
29
+ content: string;
30
+ }>;
31
+ }) => Promise<{
32
+ content: Array<{
33
+ type: string;
34
+ text?: string;
35
+ }>;
36
+ }>;
37
+ };
38
+ }
39
+ export interface ProposeDiffOptions {
40
+ client: HaikuClientLike;
41
+ skillMd: string;
42
+ aggregate: AggregateResult;
43
+ model?: string;
44
+ maxTokens?: number;
45
+ }
46
+ /**
47
+ * Render the user-facing prompt. Exposed for test introspection — stable
48
+ * string shape is part of the determinism contract (same inputs → same
49
+ * prompt → same completion at temp 0).
50
+ */
51
+ export declare function buildUserPrompt(skillMd: string, aggregate: AggregateResult): string;
52
+ /**
53
+ * Call Haiku with temperature 0 and extract the proposed {diff, rationale}.
54
+ *
55
+ * Throws when the model returns malformed JSON — callers should surface the
56
+ * error so the user can retry or abort (we never silently fall back to a
57
+ * blank diff, because that would suggest "no change needed" when the truth
58
+ * is "the model broke").
59
+ */
60
+ export declare function proposeDiff(opts: ProposeDiffOptions): Promise<HaikuDiffResult>;
61
+ /**
62
+ * Extract the JSON object from a Haiku completion. Tolerates ```json fences
63
+ * in case the model slips past the system instruction.
64
+ */
65
+ export declare function parseHaikuJson(raw: string): HaikuDiffResult;
66
+ //# sourceMappingURL=haiku-diff.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"haiku-diff.d.ts","sourceRoot":"","sources":["../../../../src/core/skill-refine/haiku-diff.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAEpD,eAAO,MAAM,WAAW,8BAA8B,CAAC;AAEvD,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE;QACR,MAAM,EAAE,CAAC,IAAI,EAAE;YACb,KAAK,EAAE,MAAM,CAAC;YACd,UAAU,EAAE,MAAM,CAAC;YACnB,WAAW,EAAE,MAAM,CAAC;YACpB,MAAM,CAAC,EAAE,MAAM,CAAC;YAChB,QAAQ,EAAE,KAAK,CAAC;gBAAE,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;gBAAC,OAAO,EAAE,MAAM,CAAA;aAAE,CAAC,CAAC;SAClE,KAAK,OAAO,CAAC;YACZ,OAAO,EAAE,KAAK,CAAC;gBAAE,IAAI,EAAE,MAAM,CAAC;gBAAC,IAAI,CAAC,EAAE,MAAM,CAAA;aAAE,CAAC,CAAC;SACjD,CAAC,CAAC;KACJ,CAAC;CACH;AAED,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,eAAe,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,eAAe,CAAC;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAgBD;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,eAAe,GAAG,MAAM,CAyBnF;AAED;;;;;;;GAOG;AACH,wBAAsB,WAAW,CAC/B,IAAI,EAAE,kBAAkB,GACvB,OAAO,CAAC,eAAe,CAAC,CAiB1B;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,eAAe,CA4B3D"}
@@ -0,0 +1,111 @@
1
+ /**
2
+ * Haiku-backed diff proposer for `sw:skill-refine`.
3
+ *
4
+ * Given the current SKILL.md contents and an aggregate of refinement signals,
5
+ * asks Claude Haiku for a unified-diff proposal and a short rationale. The
6
+ * model is invoked at `temperature: 0` so repeat runs with the same inputs
7
+ * yield byte-identical output (AC-US2-02 determinism check).
8
+ *
9
+ * The Anthropic client is injected via the `client` option so tests can stub
10
+ * the call — production callers pass the real SDK instance.
11
+ *
12
+ * @module core/skill-refine/haiku-diff
13
+ */
14
+ export const HAIKU_MODEL = 'claude-haiku-4-5-20251001';
15
+ const SYSTEM_PROMPT = [
16
+ 'You are an expert editor of Claude Code SKILL.md files.',
17
+ 'A SpecWeave user has collected refinement signals showing places where a skill\'s instructions led to gate failures.',
18
+ 'Propose a MINIMAL unified diff against the current SKILL.md that addresses the signals.',
19
+ '',
20
+ 'Requirements:',
21
+ '- Output a single JSON object with keys "diff" (a unified-diff string) and "rationale" (one sentence).',
22
+ '- The diff MUST be applicable with `git apply` (standard unified format, "--- a/SKILL.md" / "+++ b/SKILL.md").',
23
+ '- Do not restructure the document; make targeted edits that address the cited evidence.',
24
+ '- If the signals do not warrant a change, return {"diff":"","rationale":"No change warranted — signals insufficient."}.',
25
+ '- Never edit frontmatter unless signals specifically name it.',
26
+ '- No markdown fences around the JSON. No commentary.',
27
+ ].join('\n');
28
+ /**
29
+ * Render the user-facing prompt. Exposed for test introspection — stable
30
+ * string shape is part of the determinism contract (same inputs → same
31
+ * prompt → same completion at temp 0).
32
+ */
33
+ export function buildUserPrompt(skillMd, aggregate) {
34
+ const severity = aggregate.bySeverity;
35
+ const source = aggregate.bySource;
36
+ const evidenceBlock = aggregate.signals
37
+ .map((s, i) => `${i + 1}. [${s.severity}/${s.source}/${s.incrementId}] ${s.evidence}`)
38
+ .join('\n');
39
+ return [
40
+ `Target skill: ${aggregate.targetSkill}`,
41
+ `Window: last ${aggregate.window.lastNIncrements} increments — ${aggregate.incrementIds.join(', ') || '(none)'}`,
42
+ `Signals: ${aggregate.totalSignals} total | by-source judge-llm=${source['judge-llm']} rubric=${source.rubric} code-reviewer=${source['code-reviewer']} | by-severity high=${severity.high} medium=${severity.medium} low=${severity.low}`,
43
+ '',
44
+ 'Evidence:',
45
+ evidenceBlock || '(no signals)',
46
+ '',
47
+ '--- BEGIN CURRENT SKILL.md ---',
48
+ skillMd,
49
+ '--- END CURRENT SKILL.md ---',
50
+ '',
51
+ 'Return the JSON object now.',
52
+ ].join('\n');
53
+ }
54
+ /**
55
+ * Call Haiku with temperature 0 and extract the proposed {diff, rationale}.
56
+ *
57
+ * Throws when the model returns malformed JSON — callers should surface the
58
+ * error so the user can retry or abort (we never silently fall back to a
59
+ * blank diff, because that would suggest "no change needed" when the truth
60
+ * is "the model broke").
61
+ */
62
+ export async function proposeDiff(opts) {
63
+ const userPrompt = buildUserPrompt(opts.skillMd, opts.aggregate);
64
+ const response = await opts.client.messages.create({
65
+ model: opts.model ?? HAIKU_MODEL,
66
+ max_tokens: opts.maxTokens ?? 4096,
67
+ temperature: 0,
68
+ system: SYSTEM_PROMPT,
69
+ messages: [{ role: 'user', content: userPrompt }],
70
+ });
71
+ const text = (response.content ?? [])
72
+ .filter((b) => b.type === 'text' && typeof b.text === 'string')
73
+ .map((b) => b.text)
74
+ .join('\n')
75
+ .trim();
76
+ return parseHaikuJson(text);
77
+ }
78
+ /**
79
+ * Extract the JSON object from a Haiku completion. Tolerates ```json fences
80
+ * in case the model slips past the system instruction.
81
+ */
82
+ export function parseHaikuJson(raw) {
83
+ const cleaned = stripFences(raw);
84
+ let parsed;
85
+ try {
86
+ parsed = JSON.parse(cleaned);
87
+ }
88
+ catch {
89
+ throw new Error(`haiku-diff: model returned non-JSON output: ${truncate(raw, 200)}`);
90
+ }
91
+ if (parsed &&
92
+ typeof parsed === 'object' &&
93
+ 'diff' in parsed &&
94
+ 'rationale' in parsed &&
95
+ typeof parsed.diff === 'string' &&
96
+ typeof parsed.rationale === 'string') {
97
+ return {
98
+ diff: parsed.diff,
99
+ rationale: parsed.rationale,
100
+ };
101
+ }
102
+ throw new Error(`haiku-diff: JSON missing required keys {diff, rationale}: ${truncate(cleaned, 200)}`);
103
+ }
104
+ function stripFences(text) {
105
+ const fenced = text.match(/```(?:json)?\s*\n([\s\S]*?)\n```/);
106
+ return fenced ? fenced[1].trim() : text.trim();
107
+ }
108
+ function truncate(text, max) {
109
+ return text.length <= max ? text : text.slice(0, max) + '…';
110
+ }
111
+ //# sourceMappingURL=haiku-diff.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"haiku-diff.js","sourceRoot":"","sources":["../../../../src/core/skill-refine/haiku-diff.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAIH,MAAM,CAAC,MAAM,WAAW,GAAG,2BAA2B,CAAC;AA6BvD,MAAM,aAAa,GAAG;IACpB,yDAAyD;IACzD,sHAAsH;IACtH,yFAAyF;IACzF,EAAE;IACF,eAAe;IACf,wGAAwG;IACxG,gHAAgH;IAChH,yFAAyF;IACzF,yHAAyH;IACzH,+DAA+D;IAC/D,sDAAsD;CACvD,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAEb;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,OAAe,EAAE,SAA0B;IACzE,MAAM,QAAQ,GAAG,SAAS,CAAC,UAAU,CAAC;IACtC,MAAM,MAAM,GAAG,SAAS,CAAC,QAAQ,CAAC;IAElC,MAAM,aAAa,GAAG,SAAS,CAAC,OAAO;SACpC,GAAG,CACF,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CACP,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,QAAQ,IAAI,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,WAAW,KAAK,CAAC,CAAC,QAAQ,EAAE,CACzE;SACA,IAAI,CAAC,IAAI,CAAC,CAAC;IAEd,OAAO;QACL,iBAAiB,SAAS,CAAC,WAAW,EAAE;QACxC,gBAAgB,SAAS,CAAC,MAAM,CAAC,eAAe,iBAAiB,SAAS,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,QAAQ,EAAE;QAChH,YAAY,SAAS,CAAC,YAAY,gCAAgC,MAAM,CAAC,WAAW,CAAC,WAAW,MAAM,CAAC,MAAM,kBAAkB,MAAM,CAAC,eAAe,CAAC,uBAAuB,QAAQ,CAAC,IAAI,WAAW,QAAQ,CAAC,MAAM,QAAQ,QAAQ,CAAC,GAAG,EAAE;QAC1O,EAAE;QACF,WAAW;QACX,aAAa,IAAI,cAAc;QAC/B,EAAE;QACF,gCAAgC;QAChC,OAAO;QACP,8BAA8B;QAC9B,EAAE;QACF,6BAA6B;KAC9B,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,IAAwB;IAExB,MAAM,UAAU,GAAG,eAAe,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;IACjE,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC;QACjD,KAAK,EAAE,IAAI,CAAC,KAAK,IAAI,WAAW;QAChC,UAAU,EAAE,IAAI,CAAC,SAAS,IAAI,IAAI;QAClC,WAAW,EAAE,CAAC;QACd,MAAM,EAAE,aAAa;QACrB,QAAQ,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,CAAC;KAClD,CAAC,CAAC;IAEH,MAAM,IAAI,GAAG,CAAC,QAAQ,CAAC,OAAO,IAAI,EAAE,CAAC;SAClC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,IAAI,OAAO,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC;SAC9D,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAc,CAAC;SAC5B,IAAI,CAAC,IAAI,CAAC;SACV,IAAI,EAAE,CAAC;IAEV,OAAO,cAAc,CAAC,IAAI,CAAC,CAAC;AAC9B,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,GAAW;IACxC,MAAM,OAAO,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC;IACjC,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAC/B,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,KAAK,CACb,+CAA+C,QAAQ,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE,CACpE,CAAC;IACJ,CAAC;IAED,IACE,MAAM;QACN,OAAO,MAAM,KAAK,QAAQ;QAC1B,MAAM,IAAI,MAAM;QAChB,WAAW,IAAI,MAAM;QACrB,OAAQ,MAAc,CAAC,IAAI,KAAK,QAAQ;QACxC,OAAQ,MAAc,CAAC,SAAS,KAAK,QAAQ,EAC7C,CAAC;QACD,OAAO;YACL,IAAI,EAAG,MAAc,CAAC,IAAI;YAC1B,SAAS,EAAG,MAAc,CAAC,SAAS;SACrC,CAAC;IACJ,CAAC;IAED,MAAM,IAAI,KAAK,CACb,6DAA6D,QAAQ,CAAC,OAAO,EAAE,GAAG,CAAC,EAAE,CACtF,CAAC;AACJ,CAAC;AAED,SAAS,WAAW,CAAC,IAAY;IAC/B,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,kCAAkC,CAAC,CAAC;IAC9D,OAAO,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;AACjD,CAAC;AAED,SAAS,QAAQ,CAAC,IAAY,EAAE,GAAW;IACzC,OAAO,IAAI,CAAC,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,GAAG,GAAG,CAAC;AAC9D,CAAC"}
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Append-only ledger for applied/rejected skill refinements.
3
+ *
4
+ * File lives at `.specweave/state/skill-refinements.json`. Missing/corrupt
5
+ * files degrade to an empty ledger — readers never throw.
6
+ *
7
+ * @module core/skill-refine/ledger
8
+ */
9
+ import { EMPTY_LEDGER, type RefinementLedger, type RefinementLedgerEntry, type RefinementStatus } from '../../types/skill-refinements';
10
+ export interface AppendAppliedInput {
11
+ targetSkill: string;
12
+ author: string;
13
+ signalIds: string[];
14
+ diffSha: string;
15
+ rationale: string;
16
+ /** Override appliedAt (mainly for tests). */
17
+ appliedAt?: string;
18
+ /** Override id (mainly for tests). */
19
+ id?: string;
20
+ }
21
+ export interface AppendRejectedInput {
22
+ targetSkill: string;
23
+ author: string;
24
+ signalIds: string[];
25
+ rationale: string;
26
+ rejectionReason: string;
27
+ appliedAt?: string;
28
+ id?: string;
29
+ }
30
+ export declare function readLedger(filePath: string): Promise<RefinementLedger>;
31
+ export declare function appendApplied(filePath: string, input: AppendAppliedInput): Promise<RefinementLedgerEntry>;
32
+ export declare function appendRejected(filePath: string, input: AppendRejectedInput): Promise<RefinementLedgerEntry>;
33
+ export { EMPTY_LEDGER, type RefinementLedger, type RefinementLedgerEntry, type RefinementStatus };
34
+ //# sourceMappingURL=ledger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ledger.d.ts","sourceRoot":"","sources":["../../../../src/core/skill-refine/ledger.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAKH,OAAO,EACL,YAAY,EAEZ,KAAK,gBAAgB,EACrB,KAAK,qBAAqB,EAC1B,KAAK,gBAAgB,EACtB,MAAM,+BAA+B,CAAC;AAEvC,MAAM,WAAW,kBAAkB;IACjC,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,6CAA6C;IAC7C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,sCAAsC;IACtC,EAAE,CAAC,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,mBAAmB;IAClC,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,MAAM,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,EAAE,CAAC,EAAE,MAAM,CAAC;CACb;AAED,wBAAsB,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAkB5E;AAED,wBAAsB,aAAa,CACjC,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,kBAAkB,GACxB,OAAO,CAAC,qBAAqB,CAAC,CAahC;AAED,wBAAsB,cAAc,CAClC,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,mBAAmB,GACzB,OAAO,CAAC,qBAAqB,CAAC,CAchC;AA2BD,OAAO,EAAE,YAAY,EAAE,KAAK,gBAAgB,EAAE,KAAK,qBAAqB,EAAE,KAAK,gBAAgB,EAAE,CAAC"}
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Append-only ledger for applied/rejected skill refinements.
3
+ *
4
+ * File lives at `.specweave/state/skill-refinements.json`. Missing/corrupt
5
+ * files degrade to an empty ledger — readers never throw.
6
+ *
7
+ * @module core/skill-refine/ledger
8
+ */
9
+ import { randomUUID } from 'crypto';
10
+ import { mkdir, readFile, writeFile, copyFile } from 'fs/promises';
11
+ import { dirname } from 'path';
12
+ import { EMPTY_LEDGER, RefinementLedgerSchema, } from '../../types/skill-refinements';
13
+ export async function readLedger(filePath) {
14
+ let raw;
15
+ try {
16
+ const content = await readFile(filePath, 'utf-8');
17
+ raw = JSON.parse(content);
18
+ }
19
+ catch (err) {
20
+ if (err?.code === 'ENOENT') {
21
+ return { refinements: [] };
22
+ }
23
+ await backup(filePath);
24
+ return { refinements: [] };
25
+ }
26
+ const parsed = RefinementLedgerSchema.safeParse(raw);
27
+ if (parsed.success)
28
+ return parsed.data;
29
+ await backup(filePath);
30
+ return { refinements: [] };
31
+ }
32
+ export async function appendApplied(filePath, input) {
33
+ const entry = {
34
+ id: input.id ?? generateRefinementId(),
35
+ targetSkill: input.targetSkill,
36
+ author: input.author,
37
+ signalIds: [...input.signalIds],
38
+ diffSha: input.diffSha,
39
+ rationale: input.rationale,
40
+ appliedAt: input.appliedAt ?? new Date().toISOString(),
41
+ status: 'applied',
42
+ };
43
+ await persistAppend(filePath, entry);
44
+ return entry;
45
+ }
46
+ export async function appendRejected(filePath, input) {
47
+ const entry = {
48
+ id: input.id ?? generateRefinementId(),
49
+ targetSkill: input.targetSkill,
50
+ author: input.author,
51
+ signalIds: [...input.signalIds],
52
+ diffSha: '',
53
+ rationale: input.rationale,
54
+ appliedAt: input.appliedAt ?? new Date().toISOString(),
55
+ status: 'rejected',
56
+ rejectionReason: input.rejectionReason,
57
+ };
58
+ await persistAppend(filePath, entry);
59
+ return entry;
60
+ }
61
+ // ── Internals ────────────────────────────────────────────────────────
62
+ async function persistAppend(filePath, entry) {
63
+ const current = await readLedger(filePath);
64
+ current.refinements.push(entry);
65
+ await mkdir(dirname(filePath), { recursive: true });
66
+ await writeFile(filePath, JSON.stringify(current, null, 2), 'utf-8');
67
+ }
68
+ async function backup(filePath) {
69
+ try {
70
+ await copyFile(filePath, filePath + '.bak');
71
+ }
72
+ catch {
73
+ // best-effort
74
+ }
75
+ }
76
+ function generateRefinementId() {
77
+ return `ref_${randomUUID().replace(/-/g, '').slice(0, 20).toUpperCase()}`;
78
+ }
79
+ // Re-export for convenience.
80
+ export { EMPTY_LEDGER };
81
+ //# sourceMappingURL=ledger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ledger.js","sourceRoot":"","sources":["../../../../src/core/skill-refine/ledger.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AACpC,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACnE,OAAO,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAC/B,OAAO,EACL,YAAY,EACZ,sBAAsB,GAIvB,MAAM,+BAA+B,CAAC;AAwBvC,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,QAAgB;IAC/C,IAAI,GAAY,CAAC;IACjB,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QAClD,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAC5B,CAAC;IAAC,OAAO,GAAQ,EAAE,CAAC;QAClB,IAAI,GAAG,EAAE,IAAI,KAAK,QAAQ,EAAE,CAAC;YAC3B,OAAO,EAAE,WAAW,EAAE,EAAE,EAAE,CAAC;QAC7B,CAAC;QACD,MAAM,MAAM,CAAC,QAAQ,CAAC,CAAC;QACvB,OAAO,EAAE,WAAW,EAAE,EAAE,EAAE,CAAC;IAC7B,CAAC;IAED,MAAM,MAAM,GAAG,sBAAsB,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;IACrD,IAAI,MAAM,CAAC,OAAO;QAAE,OAAO,MAAM,CAAC,IAAI,CAAC;IAEvC,MAAM,MAAM,CAAC,QAAQ,CAAC,CAAC;IACvB,OAAO,EAAE,WAAW,EAAE,EAAE,EAAE,CAAC;AAC7B,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,QAAgB,EAChB,KAAyB;IAEzB,MAAM,KAAK,GAA0B;QACnC,EAAE,EAAE,KAAK,CAAC,EAAE,IAAI,oBAAoB,EAAE;QACtC,WAAW,EAAE,KAAK,CAAC,WAAW;QAC9B,MAAM,EAAE,KAAK,CAAC,MAAM;QACpB,SAAS,EAAE,CAAC,GAAG,KAAK,CAAC,SAAS,CAAC;QAC/B,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,SAAS,EAAE,KAAK,CAAC,SAAS;QAC1B,SAAS,EAAE,KAAK,CAAC,SAAS,IAAI,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QACtD,MAAM,EAAE,SAAS;KAClB,CAAC;IACF,MAAM,aAAa,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;IACrC,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,QAAgB,EAChB,KAA0B;IAE1B,MAAM,KAAK,GAA0B;QACnC,EAAE,EAAE,KAAK,CAAC,EAAE,IAAI,oBAAoB,EAAE;QACtC,WAAW,EAAE,KAAK,CAAC,WAAW;QAC9B,MAAM,EAAE,KAAK,CAAC,MAAM;QACpB,SAAS,EAAE,CAAC,GAAG,KAAK,CAAC,SAAS,CAAC;QAC/B,OAAO,EAAE,EAAE;QACX,SAAS,EAAE,KAAK,CAAC,SAAS;QAC1B,SAAS,EAAE,KAAK,CAAC,SAAS,IAAI,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QACtD,MAAM,EAAE,UAAU;QAClB,eAAe,EAAE,KAAK,CAAC,eAAe;KACvC,CAAC;IACF,MAAM,aAAa,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;IACrC,OAAO,KAAK,CAAC;AACf,CAAC;AAED,wEAAwE;AAExE,KAAK,UAAU,aAAa,CAC1B,QAAgB,EAChB,KAA4B;IAE5B,MAAM,OAAO,GAAG,MAAM,UAAU,CAAC,QAAQ,CAAC,CAAC;IAC3C,OAAO,CAAC,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAChC,MAAM,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACpD,MAAM,SAAS,CAAC,QAAQ,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;AACvE,CAAC;AAED,KAAK,UAAU,MAAM,CAAC,QAAgB;IACpC,IAAI,CAAC;QACH,MAAM,QAAQ,CAAC,QAAQ,EAAE,QAAQ,GAAG,MAAM,CAAC,CAAC;IAC9C,CAAC;IAAC,MAAM,CAAC;QACP,cAAc;IAChB,CAAC;AACH,CAAC;AAED,SAAS,oBAAoB;IAC3B,OAAO,OAAO,UAAU,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,WAAW,EAAE,EAAE,CAAC;AAC5E,CAAC;AAED,6BAA6B;AAC7B,OAAO,EAAE,YAAY,EAA4E,CAAC"}