specweave 1.0.587 → 1.0.589

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 (38) hide show
  1. package/dist/dashboard/assets/{index-DdI7mc7Y.js → index-DH3l1QBV.js} +6 -6
  2. package/dist/dashboard/assets/index-vkwF92tD.css +1 -0
  3. package/dist/dashboard/index.html +2 -2
  4. package/dist/plugins/specweave/lib/utils/fs-native.d.ts +1 -1
  5. package/dist/plugins/specweave/lib/vendor/utils/fs-native.d.ts +1 -1
  6. package/dist/src/cli/helpers/init/external-import.js.map +1 -1
  7. package/dist/src/core/hooks/handlers/hook-router.d.ts.map +1 -1
  8. package/dist/src/core/hooks/handlers/hook-router.js +9 -0
  9. package/dist/src/core/hooks/handlers/hook-router.js.map +1 -1
  10. package/dist/src/core/hooks/handlers/post-tool-use-analytics.d.ts +11 -0
  11. package/dist/src/core/hooks/handlers/post-tool-use-analytics.d.ts.map +1 -0
  12. package/dist/src/core/hooks/handlers/post-tool-use-analytics.js +75 -0
  13. package/dist/src/core/hooks/handlers/post-tool-use-analytics.js.map +1 -0
  14. package/dist/src/core/hooks/handlers/post-tool-use.d.ts +11 -0
  15. package/dist/src/core/hooks/handlers/post-tool-use.d.ts.map +1 -0
  16. package/dist/src/core/hooks/handlers/post-tool-use.js +76 -0
  17. package/dist/src/core/hooks/handlers/post-tool-use.js.map +1 -0
  18. package/dist/src/core/hooks/handlers/session-start.d.ts +9 -0
  19. package/dist/src/core/hooks/handlers/session-start.d.ts.map +1 -0
  20. package/dist/src/core/hooks/handlers/session-start.js +111 -0
  21. package/dist/src/core/hooks/handlers/session-start.js.map +1 -0
  22. package/dist/src/core/hooks/handlers/stop-auto.d.ts +22 -0
  23. package/dist/src/core/hooks/handlers/stop-auto.d.ts.map +1 -0
  24. package/dist/src/core/hooks/handlers/stop-auto.js +227 -0
  25. package/dist/src/core/hooks/handlers/stop-auto.js.map +1 -0
  26. package/dist/src/core/hooks/handlers/stop-reflect.d.ts +14 -0
  27. package/dist/src/core/hooks/handlers/stop-reflect.d.ts.map +1 -0
  28. package/dist/src/core/hooks/handlers/stop-reflect.js +43 -0
  29. package/dist/src/core/hooks/handlers/stop-reflect.js.map +1 -0
  30. package/dist/src/core/hooks/handlers/stop-sync.d.ts +15 -0
  31. package/dist/src/core/hooks/handlers/stop-sync.d.ts.map +1 -0
  32. package/dist/src/core/hooks/handlers/stop-sync.js +68 -0
  33. package/dist/src/core/hooks/handlers/stop-sync.js.map +1 -0
  34. package/dist/src/utils/fs-native.d.ts +1 -1
  35. package/package.json +2 -2
  36. package/plugins/specweave/hooks/hooks.json +5 -0
  37. package/plugins/specweave/lib/vendor/utils/fs-native.d.ts +1 -1
  38. package/dist/dashboard/assets/index-YkogWPHY.css +0 -1
@@ -0,0 +1,111 @@
1
+ /**
2
+ * Session-start hook handler — session initialization.
3
+ *
4
+ * Clears stale auto-mode files, resets context pressure,
5
+ * and performs baseline prompt health check.
6
+ */
7
+ import * as fs from 'fs';
8
+ import * as path from 'path';
9
+ const STALE_THRESHOLD_MS = 24 * 60 * 60 * 1000; // 24 hours
10
+ const WARNING_THRESHOLD = 80000;
11
+ const CRITICAL_THRESHOLD = 120000;
12
+ const SYSTEM_ESTIMATE = 12000;
13
+ const SKILL_BUDGET = 15000;
14
+ const HOOK_PER_TURN = 3000;
15
+ function isStale(filePath) {
16
+ try {
17
+ const stat = fs.statSync(filePath);
18
+ return Date.now() - stat.mtimeMs > STALE_THRESHOLD_MS;
19
+ }
20
+ catch {
21
+ return false;
22
+ }
23
+ }
24
+ function safeRemove(filePath) {
25
+ try {
26
+ fs.unlinkSync(filePath);
27
+ }
28
+ catch {
29
+ // Ignore
30
+ }
31
+ }
32
+ function fileSize(filePath) {
33
+ try {
34
+ return fs.statSync(filePath).size;
35
+ }
36
+ catch {
37
+ return 0;
38
+ }
39
+ }
40
+ export const handle = async (_input, context) => {
41
+ const { projectRoot, stateDir, logsDir, timestamp } = context;
42
+ try {
43
+ fs.mkdirSync(stateDir, { recursive: true });
44
+ }
45
+ catch {
46
+ return { continue: true };
47
+ }
48
+ // --- Auto-mode cleanup: remove stale session files (>24h) ---
49
+ const autoFile = path.join(stateDir, 'auto-mode.json');
50
+ if (fs.existsSync(autoFile) && isStale(autoFile)) {
51
+ safeRemove(autoFile);
52
+ safeRemove(path.join(stateDir, '.stop-auto-dedup'));
53
+ safeRemove(path.join(stateDir, '.stop-auto-dedup-prev'));
54
+ safeRemove(path.join(stateDir, '.stop-auto-turns'));
55
+ try {
56
+ fs.mkdirSync(logsDir, { recursive: true });
57
+ fs.appendFileSync(path.join(logsDir, 'session.log'), `[${timestamp}] SessionStart: Cleared stale auto-mode session files (>24h)\n`);
58
+ }
59
+ catch {
60
+ // Never throw from logging
61
+ }
62
+ }
63
+ // --- Context pressure reset ---
64
+ safeRemove(path.join(stateDir, 'context-pressure.json'));
65
+ safeRemove(path.join(stateDir, 'prompt-health-alert.json'));
66
+ // --- Baseline health check ---
67
+ try {
68
+ const claudeMdSize = fileSize(path.join(projectRoot, 'CLAUDE.md'));
69
+ const memoryDir = path.join(projectRoot, '.claude', 'projects');
70
+ let memoryMdSize = 0;
71
+ try {
72
+ // Find MEMORY.md in any project subdirectory
73
+ if (fs.existsSync(memoryDir)) {
74
+ const entries = fs.readdirSync(memoryDir, { withFileTypes: true });
75
+ for (const entry of entries) {
76
+ if (entry.isDirectory()) {
77
+ const memPath = path.join(memoryDir, entry.name, 'memory', 'MEMORY.md');
78
+ memoryMdSize += fileSize(memPath);
79
+ }
80
+ }
81
+ }
82
+ }
83
+ catch {
84
+ // Best-effort
85
+ }
86
+ const baseline = claudeMdSize + memoryMdSize + SYSTEM_ESTIMATE + SKILL_BUDGET + HOOK_PER_TURN;
87
+ let warningLevel = 'normal';
88
+ if (baseline > CRITICAL_THRESHOLD) {
89
+ warningLevel = 'critical';
90
+ }
91
+ else if (baseline > WARNING_THRESHOLD) {
92
+ warningLevel = 'warning';
93
+ }
94
+ const health = {
95
+ baseline,
96
+ claudeMdSize,
97
+ memoryMdSize,
98
+ skillBudget: SKILL_BUDGET,
99
+ systemEstimate: SYSTEM_ESTIMATE,
100
+ hookPerTurn: HOOK_PER_TURN,
101
+ warningLevel,
102
+ checkedAt: timestamp,
103
+ };
104
+ fs.writeFileSync(path.join(stateDir, 'prompt-health.json'), JSON.stringify(health));
105
+ }
106
+ catch {
107
+ // Health check is best-effort
108
+ }
109
+ return { continue: true };
110
+ };
111
+ //# sourceMappingURL=session-start.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-start.js","sourceRoot":"","sources":["../../../../../src/core/hooks/handlers/session-start.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Stop-auto hook handler — the autonomous-loop driver for `/sw:auto`.
3
+ *
4
+ * Fires when a session ends. Unlike the other Stop hooks, this one is allowed to
5
+ * BLOCK so that Claude Code re-prompts the model and the auto loop continues
6
+ * (see plugins/specweave/skills/auto/SKILL.md "Core Loop"):
7
+ *
8
+ * IMPLEMENT -> TEST -> FIX -> PASS -> NEXT -> ... -> ALL DONE -> sw:done --auto
9
+ *
10
+ * Decision table (any throw short-circuits to `approve` so ordinary sessions are
11
+ * never trapped):
12
+ *
13
+ * no auto-mode.json | active !== true | stale -> reset .stop-auto-turns; { approve }
14
+ * turns > auto.maxTurns (default 20) -> clear counter; { approve } // safety stop
15
+ * 0 pending tasks AND all ACs satisfied -> clear counter; { block: all_complete_needs_closure }
16
+ * otherwise (work remains) -> increment counter; { block: <P> task(s) / <A> AC(s) remain }
17
+ *
18
+ * @module core/hooks/handlers/stop-auto
19
+ */
20
+ import type { HandlerFn } from './types.js';
21
+ export declare const handle: HandlerFn;
22
+ //# sourceMappingURL=stop-auto.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stop-auto.d.ts","sourceRoot":"","sources":["../../../../../src/core/hooks/handlers/stop-auto.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAIH,OAAO,KAAK,EAAE,SAAS,EAAe,MAAM,YAAY,CAAC;AAsJzD,eAAO,MAAM,MAAM,EAAE,SAiEpB,CAAC"}
@@ -0,0 +1,227 @@
1
+ /**
2
+ * Stop-auto hook handler — the autonomous-loop driver for `/sw:auto`.
3
+ *
4
+ * Fires when a session ends. Unlike the other Stop hooks, this one is allowed to
5
+ * BLOCK so that Claude Code re-prompts the model and the auto loop continues
6
+ * (see plugins/specweave/skills/auto/SKILL.md "Core Loop"):
7
+ *
8
+ * IMPLEMENT -> TEST -> FIX -> PASS -> NEXT -> ... -> ALL DONE -> sw:done --auto
9
+ *
10
+ * Decision table (any throw short-circuits to `approve` so ordinary sessions are
11
+ * never trapped):
12
+ *
13
+ * no auto-mode.json | active !== true | stale -> reset .stop-auto-turns; { approve }
14
+ * turns > auto.maxTurns (default 20) -> clear counter; { approve } // safety stop
15
+ * 0 pending tasks AND all ACs satisfied -> clear counter; { block: all_complete_needs_closure }
16
+ * otherwise (work remains) -> increment counter; { block: <P> task(s) / <A> AC(s) remain }
17
+ *
18
+ * @module core/hooks/handlers/stop-auto
19
+ */
20
+ import * as fs from 'fs';
21
+ import * as path from 'path';
22
+ import { logHook } from './utils.js';
23
+ import { calculateProgressFromTasksFile } from '../../../progress/us-progress-tracker.js';
24
+ /** Read JSON safely, return null on error */
25
+ function readJsonSafe(filePath) {
26
+ try {
27
+ return JSON.parse(fs.readFileSync(filePath, 'utf8'));
28
+ }
29
+ catch {
30
+ return null;
31
+ }
32
+ }
33
+ /** Resolve the auto-mode session file (per-session if present, else global). */
34
+ function getSessionPath(stateDir) {
35
+ const sessionId = process.env.CLAUDE_SESSION_ID;
36
+ if (sessionId) {
37
+ const perSession = path.join(stateDir, 'sessions', sessionId, 'auto-mode.json');
38
+ if (fs.existsSync(perSession))
39
+ return perSession;
40
+ }
41
+ return path.join(stateDir, 'auto-mode.json');
42
+ }
43
+ /** True when the session file's mtime is older than maxSessionAge seconds. */
44
+ function isSessionStale(sessionPath, maxSessionAge) {
45
+ try {
46
+ const ageMs = Date.now() - fs.statSync(sessionPath).mtimeMs;
47
+ return ageMs > maxSessionAge * 1000;
48
+ }
49
+ catch {
50
+ return true;
51
+ }
52
+ }
53
+ const TURNS_FILE = '.stop-auto-turns';
54
+ function readTurns(stateDir) {
55
+ try {
56
+ const n = parseInt(fs.readFileSync(path.join(stateDir, TURNS_FILE), 'utf8').trim(), 10);
57
+ return Number.isFinite(n) && n >= 0 ? n : 0;
58
+ }
59
+ catch {
60
+ return 0;
61
+ }
62
+ }
63
+ function writeTurns(stateDir, n) {
64
+ try {
65
+ fs.mkdirSync(stateDir, { recursive: true });
66
+ fs.writeFileSync(path.join(stateDir, TURNS_FILE), String(n));
67
+ }
68
+ catch {
69
+ // Never throw from counter bookkeeping
70
+ }
71
+ }
72
+ function resetTurns(stateDir) {
73
+ try {
74
+ fs.unlinkSync(path.join(stateDir, TURNS_FILE));
75
+ }
76
+ catch {
77
+ // Already absent — fine
78
+ }
79
+ }
80
+ /** Count satisfied/total ACs from a spec.md by `- [ ]`/`- [x]` AC lines. */
81
+ function countAcs(specPath) {
82
+ let total = 0;
83
+ let satisfied = 0;
84
+ try {
85
+ const text = fs.readFileSync(specPath, 'utf8');
86
+ for (const line of text.split('\n')) {
87
+ // Match an AC checkbox line: `- [ ] **AC-US1-01**: ...` (id optional but must mention AC).
88
+ const m = line.match(/^\s*-\s*\[([ xX])\]\s*\*{0,2}AC[-\w]/);
89
+ if (!m)
90
+ continue;
91
+ total++;
92
+ if (m[1].toLowerCase() === 'x')
93
+ satisfied++;
94
+ }
95
+ }
96
+ catch {
97
+ // No spec / unreadable — treat as no ACs.
98
+ }
99
+ return { total, satisfied };
100
+ }
101
+ /** Increment ids from the session marker, falling back to an active-increment scan. */
102
+ function resolveIncrementIds(session, projectRoot) {
103
+ const fromMarker = Array.isArray(session.incrementIds)
104
+ ? session.incrementIds.filter((x) => typeof x === 'string')
105
+ : [];
106
+ if (fromMarker.length > 0)
107
+ return fromMarker;
108
+ const incDir = path.join(projectRoot, '.specweave', 'increments');
109
+ const active = [];
110
+ try {
111
+ for (const dir of fs.readdirSync(incDir)) {
112
+ const meta = readJsonSafe(path.join(incDir, dir, 'metadata.json'));
113
+ if (meta && (meta.status === 'active' || meta.status === 'in-progress')) {
114
+ active.push(dir);
115
+ }
116
+ }
117
+ }
118
+ catch {
119
+ // No increments dir — empty.
120
+ }
121
+ return active;
122
+ }
123
+ /** Resolve the on-disk increment directory for an id (exact, else prefix match). */
124
+ function findIncrementDir(projectRoot, id) {
125
+ const incDir = path.join(projectRoot, '.specweave', 'increments');
126
+ const exact = path.join(incDir, id);
127
+ if (fs.existsSync(exact))
128
+ return exact;
129
+ try {
130
+ const prefix = id.match(/^\d{4}/)?.[0] ?? id;
131
+ for (const dir of fs.readdirSync(incDir)) {
132
+ if (dir.startsWith(prefix))
133
+ return path.join(incDir, dir);
134
+ }
135
+ }
136
+ catch {
137
+ // fall through
138
+ }
139
+ return null;
140
+ }
141
+ /** Aggregate remaining work (pending tasks + unsatisfied ACs) across increments. */
142
+ async function computeRemaining(projectRoot, incrementIds) {
143
+ let pendingTasks = 0;
144
+ let pendingAcs = 0;
145
+ let primaryId = incrementIds[0] ?? '';
146
+ for (const id of incrementIds) {
147
+ const dir = findIncrementDir(projectRoot, id);
148
+ if (!dir)
149
+ continue;
150
+ // Tasks — prefer the shared parser for accurate same-line/status handling.
151
+ const tasksPath = path.join(dir, 'tasks.md');
152
+ if (fs.existsSync(tasksPath)) {
153
+ try {
154
+ const progress = await calculateProgressFromTasksFile(tasksPath);
155
+ pendingTasks += progress.pendingTasks + progress.inProgressTasks;
156
+ }
157
+ catch {
158
+ // Unparseable tasks file — skip its tasks.
159
+ }
160
+ }
161
+ // ACs — parse spec.md checkbox lines.
162
+ const { total, satisfied } = countAcs(path.join(dir, 'spec.md'));
163
+ pendingAcs += total - satisfied;
164
+ }
165
+ return { pendingTasks, pendingAcs, primaryId };
166
+ }
167
+ export const handle = async (_input, context) => {
168
+ const { stateDir, projectRoot, configPath } = context;
169
+ try {
170
+ // 1. No auto session -> approve (and make sure the counter is reset).
171
+ const sessionPath = getSessionPath(stateDir);
172
+ const session = fs.existsSync(sessionPath) ? readJsonSafe(sessionPath) : null;
173
+ if (!session || session.active !== true) {
174
+ resetTurns(stateDir);
175
+ logHook(context, 'stop-auto', 'No active auto session — approve');
176
+ return { decision: 'approve' };
177
+ }
178
+ // 2. Stale session -> approve.
179
+ const config = readJsonSafe(configPath);
180
+ const maxSessionAge = config?.auto?.maxSessionAge ?? 7200;
181
+ if (isSessionStale(sessionPath, maxSessionAge)) {
182
+ resetTurns(stateDir);
183
+ logHook(context, 'stop-auto', 'Stale auto session — approve');
184
+ return { decision: 'approve' };
185
+ }
186
+ // 3. Turn-counter safety stop -> approve.
187
+ const maxTurns = config?.auto?.maxTurns ?? 20;
188
+ const turns = readTurns(stateDir);
189
+ if (turns > maxTurns) {
190
+ resetTurns(stateDir);
191
+ logHook(context, 'stop-auto', `Safety stop: turns ${turns} > maxTurns ${maxTurns} — approve`);
192
+ return { decision: 'approve' };
193
+ }
194
+ // 4. Compute remaining work.
195
+ const incrementIds = resolveIncrementIds(session, projectRoot);
196
+ // No increment to act on (empty marker + none active) -> approve rather than
197
+ // spuriously blocking for closure on a 0/0 count.
198
+ if (incrementIds.length === 0) {
199
+ resetTurns(stateDir);
200
+ logHook(context, 'stop-auto', 'Auto session active but no increment to work on — approve');
201
+ return { decision: 'approve' };
202
+ }
203
+ const { pendingTasks, pendingAcs, primaryId } = await computeRemaining(projectRoot, incrementIds);
204
+ const idLabel = primaryId || incrementIds[0] || 'active increment';
205
+ // 5. All complete -> block for closure (the trigger sw:auto consumes).
206
+ if (pendingTasks === 0 && pendingAcs === 0) {
207
+ resetTurns(stateDir);
208
+ logHook(context, 'stop-auto', `${idLabel}: all_complete_needs_closure`);
209
+ return {
210
+ decision: 'block',
211
+ reason: `${idLabel}: all_complete_needs_closure — run sw:done --auto ${idLabel}`,
212
+ };
213
+ }
214
+ // 6. Work remains -> block to continue + increment the counter.
215
+ writeTurns(stateDir, turns + 1);
216
+ const userGoal = typeof session.userGoal === 'string' && session.userGoal ? session.userGoal : '';
217
+ const reason = `${idLabel}: ${pendingTasks} task(s) / ${pendingAcs} AC(s) remain. Run sw:do to continue.` +
218
+ (userGoal ? ` Goal: ${userGoal}` : '');
219
+ logHook(context, 'stop-auto', reason);
220
+ return { decision: 'block', reason };
221
+ }
222
+ catch {
223
+ // Never throw — any failure must not trap the session.
224
+ return { decision: 'approve' };
225
+ }
226
+ };
227
+ //# sourceMappingURL=stop-auto.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stop-auto.js","sourceRoot":"","sources":["../../../../../src/core/hooks/handlers/stop-auto.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Stop-reflect hook handler.
3
+ *
4
+ * Fires when a session ends. Checks if reflection is enabled in config.
5
+ * If enabled, logs that reflection was requested (actual reflection logic
6
+ * will call `specweave reflect-stop` CLI in a follow-up).
7
+ *
8
+ * NEVER blocks — always returns approve.
9
+ *
10
+ * @module core/hooks/handlers/stop-reflect
11
+ */
12
+ import type { HandlerFn } from './types.js';
13
+ export declare const handle: HandlerFn;
14
+ //# sourceMappingURL=stop-reflect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stop-reflect.d.ts","sourceRoot":"","sources":["../../../../../src/core/hooks/handlers/stop-reflect.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAe5C,eAAO,MAAM,MAAM,EAAE,SAgBpB,CAAC"}
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Stop-reflect hook handler.
3
+ *
4
+ * Fires when a session ends. Checks if reflection is enabled in config.
5
+ * If enabled, logs that reflection was requested (actual reflection logic
6
+ * will call `specweave reflect-stop` CLI in a follow-up).
7
+ *
8
+ * NEVER blocks — always returns approve.
9
+ *
10
+ * @module core/hooks/handlers/stop-reflect
11
+ */
12
+ import * as fs from 'fs';
13
+ import { logHook } from './utils.js';
14
+ /** Check if reflect is enabled in config. Default: true */
15
+ function isReflectEnabled(configPath) {
16
+ try {
17
+ if (!fs.existsSync(configPath))
18
+ return true;
19
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
20
+ // Note: explicit false check — missing key defaults to enabled
21
+ return config.reflect?.enabled !== false;
22
+ }
23
+ catch {
24
+ return true; // Default enabled on error
25
+ }
26
+ }
27
+ export const handle = async (input, context) => {
28
+ try {
29
+ const enabled = isReflectEnabled(context.configPath);
30
+ if (!enabled) {
31
+ logHook(context, 'stop-reflect', 'Reflection disabled in config');
32
+ return { decision: 'approve' };
33
+ }
34
+ // Reflection is enabled — log intent
35
+ logHook(context, 'stop-reflect', 'Reflection requested at session end');
36
+ return { decision: 'approve' };
37
+ }
38
+ catch {
39
+ // Never throw — always approve
40
+ return { decision: 'approve' };
41
+ }
42
+ };
43
+ //# sourceMappingURL=stop-reflect.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stop-reflect.js","sourceRoot":"","sources":["../../../../../src/core/hooks/handlers/stop-reflect.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AAEzB,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAErC,2DAA2D;AAC3D,SAAS,gBAAgB,CAAC,UAAkB;IAC1C,IAAI,CAAC;QACH,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC;YAAE,OAAO,IAAI,CAAC;QAC5C,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC,CAAC;QAC/D,+DAA+D;QAC/D,OAAO,MAAM,CAAC,OAAO,EAAE,OAAO,KAAK,KAAK,CAAC;IAC3C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC,CAAC,2BAA2B;IAC1C,CAAC;AACH,CAAC;AAED,MAAM,CAAC,MAAM,MAAM,GAAc,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE;IACxD,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,gBAAgB,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;QAErD,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,OAAO,CAAC,OAAO,EAAE,cAAc,EAAE,+BAA+B,CAAC,CAAC;YAClE,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC;QACjC,CAAC;QAED,qCAAqC;QACrC,OAAO,CAAC,OAAO,EAAE,cAAc,EAAE,qCAAqC,CAAC,CAAC;QACxE,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC;IACjC,CAAC;IAAC,MAAM,CAAC;QACP,+BAA+B;QAC/B,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC;IACjC,CAAC;AACH,CAAC,CAAC"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Stop-sync hook handler.
3
+ *
4
+ * Fires at session end (after stop-reflect and stop-auto). Reads pending
5
+ * sync events, deduplicates by increment ID, logs sync intent, and clears
6
+ * the event queue. Actual sync calls (LivingDocsSync, GitHub/JIRA/ADO)
7
+ * will be added in a follow-up.
8
+ *
9
+ * NEVER blocks — always returns approve.
10
+ *
11
+ * @module core/hooks/handlers/stop-sync
12
+ */
13
+ import type { HandlerFn } from './types.js';
14
+ export declare const handle: HandlerFn;
15
+ //# sourceMappingURL=stop-sync.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stop-sync.d.ts","sourceRoot":"","sources":["../../../../../src/core/hooks/handlers/stop-sync.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAIH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAoB5C,eAAO,MAAM,MAAM,EAAE,SA+CpB,CAAC"}
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Stop-sync hook handler.
3
+ *
4
+ * Fires at session end (after stop-reflect and stop-auto). Reads pending
5
+ * sync events, deduplicates by increment ID, logs sync intent, and clears
6
+ * the event queue. Actual sync calls (LivingDocsSync, GitHub/JIRA/ADO)
7
+ * will be added in a follow-up.
8
+ *
9
+ * NEVER blocks — always returns approve.
10
+ *
11
+ * @module core/hooks/handlers/stop-sync
12
+ */
13
+ import * as fs from 'fs';
14
+ import * as path from 'path';
15
+ import { logHook } from './utils.js';
16
+ /** Parse a JSONL line safely */
17
+ function parseEventLine(line) {
18
+ try {
19
+ return JSON.parse(line);
20
+ }
21
+ catch {
22
+ return null;
23
+ }
24
+ }
25
+ export const handle = async (input, context) => {
26
+ try {
27
+ const pendingPath = path.join(context.stateDir, 'event-queue', 'pending.jsonl');
28
+ // 1. Check if pending events exist
29
+ if (!fs.existsSync(pendingPath)) {
30
+ logHook(context, 'stop-sync', 'No pending events');
31
+ return { decision: 'approve' };
32
+ }
33
+ const content = fs.readFileSync(pendingPath, 'utf8').trim();
34
+ if (!content) {
35
+ logHook(context, 'stop-sync', 'No pending events');
36
+ return { decision: 'approve' };
37
+ }
38
+ // 2. Parse and deduplicate events by increment ID
39
+ const lines = content.split('\n').filter((l) => l.trim());
40
+ const seenIncrements = new Set();
41
+ for (const line of lines) {
42
+ const event = parseEventLine(line);
43
+ if (!event?.incrementId)
44
+ continue;
45
+ const incId = event.incrementId;
46
+ if (seenIncrements.has(incId))
47
+ continue;
48
+ seenIncrements.add(incId);
49
+ logHook(context, 'stop-sync', `Sync requested for increment: ${incId} (event: ${event.event ?? 'unknown'})`);
50
+ }
51
+ // 3. Clear processed events
52
+ try {
53
+ fs.writeFileSync(pendingPath, '');
54
+ }
55
+ catch {
56
+ // Non-critical — events may be re-processed next time
57
+ }
58
+ if (seenIncrements.size > 0) {
59
+ logHook(context, 'stop-sync', `Processed ${seenIncrements.size} unique increment(s)`);
60
+ }
61
+ return { decision: 'approve' };
62
+ }
63
+ catch {
64
+ // Never throw — always approve
65
+ return { decision: 'approve' };
66
+ }
67
+ };
68
+ //# sourceMappingURL=stop-sync.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stop-sync.js","sourceRoot":"","sources":["../../../../../src/core/hooks/handlers/stop-sync.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAE7B,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAUrC,gCAAgC;AAChC,SAAS,cAAc,CAAC,IAAY;IAClC,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAgB,CAAC;IACzC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,MAAM,CAAC,MAAM,MAAM,GAAc,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE;IACxD,IAAI,CAAC;QACH,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,aAAa,EAAE,eAAe,CAAC,CAAC;QAEhF,mCAAmC;QACnC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,WAAW,CAAC,EAAE,CAAC;YAChC,OAAO,CAAC,OAAO,EAAE,WAAW,EAAE,mBAAmB,CAAC,CAAC;YACnD,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC;QACjC,CAAC;QAED,MAAM,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;QAC5D,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,OAAO,CAAC,OAAO,EAAE,WAAW,EAAE,mBAAmB,CAAC,CAAC;YACnD,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC;QACjC,CAAC;QAED,kDAAkD;QAClD,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;QAC1D,MAAM,cAAc,GAAG,IAAI,GAAG,EAAU,CAAC;QAEzC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,MAAM,KAAK,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC;YACnC,IAAI,CAAC,KAAK,EAAE,WAAW;gBAAE,SAAS;YAElC,MAAM,KAAK,GAAG,KAAK,CAAC,WAAW,CAAC;YAChC,IAAI,cAAc,CAAC,GAAG,CAAC,KAAK,CAAC;gBAAE,SAAS;YAExC,cAAc,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;YAC1B,OAAO,CAAC,OAAO,EAAE,WAAW,EAAE,iCAAiC,KAAK,YAAY,KAAK,CAAC,KAAK,IAAI,SAAS,GAAG,CAAC,CAAC;QAC/G,CAAC;QAED,4BAA4B;QAC5B,IAAI,CAAC;YACH,EAAE,CAAC,aAAa,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;QACpC,CAAC;QAAC,MAAM,CAAC;YACP,sDAAsD;QACxD,CAAC;QAED,IAAI,cAAc,CAAC,IAAI,GAAG,CAAC,EAAE,CAAC;YAC5B,OAAO,CAAC,OAAO,EAAE,WAAW,EAAE,aAAa,cAAc,CAAC,IAAI,sBAAsB,CAAC,CAAC;QACxF,CAAC;QAED,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC;IACjC,CAAC;IAAC,MAAM,CAAC;QACP,+BAA+B;QAC/B,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC;IACjC,CAAC;AACH,CAAC,CAAC"}
@@ -201,7 +201,7 @@ declare const _default: {
201
201
  ensureFileSync: typeof ensureFileSync;
202
202
  readFileSync: typeof readFileSync;
203
203
  writeFileSync: typeof writeFileSync;
204
- statSync: import("node:fs").StatSyncFn;
204
+ statSync: typeof statSync;
205
205
  readdirSync: typeof readdirSync;
206
206
  unlinkSync: typeof unlinkSync;
207
207
  mkdirSync: typeof mkdirSync;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "specweave",
3
- "version": "1.0.587",
3
+ "version": "1.0.589",
4
4
  "description": "100+ domain-expert AI skills — PM, Architect, Frontend, QA, Security and more. Skills learn your team's patterns permanently. Spec-first planning, autonomous execution, multi-agent teams, synced to GitHub/JIRA. Claude Code, Cursor, Copilot & more.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -91,7 +91,7 @@
91
91
  "LICENSE"
92
92
  ],
93
93
  "dependencies": {
94
- "@anthropic-ai/sdk": "^0.92.0",
94
+ "@anthropic-ai/sdk": "^0.100.1",
95
95
  "@inquirer/prompts": "^8.1.0",
96
96
  "@octokit/rest": "^22.0.1",
97
97
  "@types/handlebars": "^4.0.40",
@@ -89,6 +89,11 @@
89
89
  "type": "command",
90
90
  "command": "bash -c 'command -v specweave &>/dev/null && specweave hook stop-sync || { cat >/dev/null; echo \"{\\\"decision\\\":\\\"approve\\\"}\"; }'",
91
91
  "timeout": 15000
92
+ },
93
+ {
94
+ "type": "command",
95
+ "command": "bash -c 'command -v specweave &>/dev/null && specweave hook stop || { cat >/dev/null; echo \"{\\\"decision\\\":\\\"approve\\\"}\"; }'",
96
+ "timeout": 15000
92
97
  }
93
98
  ]
94
99
  }
@@ -201,7 +201,7 @@ declare const _default: {
201
201
  ensureFileSync: typeof ensureFileSync;
202
202
  readFileSync: typeof readFileSync;
203
203
  writeFileSync: typeof writeFileSync;
204
- statSync: import("node:fs").StatSyncFn;
204
+ statSync: typeof statSync;
205
205
  readdirSync: typeof readdirSync;
206
206
  unlinkSync: typeof unlinkSync;
207
207
  mkdirSync: typeof mkdirSync;