claude-alfred 0.3.3 → 0.3.5

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 (28) hide show
  1. package/README.ja.md +2 -2
  2. package/README.md +2 -2
  3. package/dist/{audit-09Rf9Awg.mjs → audit-g6phLGMg.mjs} +1 -1
  4. package/dist/cli.mjs +7 -7
  5. package/dist/{directives-Bm6w4JNk.mjs → directives-BnbWxhap.mjs} +1 -1
  6. package/dist/{dispatcher-DZUzmm1Y.mjs → dispatcher-M-Rc8GYD.mjs} +7 -7
  7. package/dist/{epic-Cde2RANp.mjs → epic-D9ksT1k7.mjs} +1 -1
  8. package/dist/{fts-Ck8wjOIE.mjs → fts-Buk8fkl1.mjs} +2 -2
  9. package/dist/{helpers-rMTa9O_h.mjs → helpers-CvI9bVCq.mjs} +3 -3
  10. package/dist/knowledge-BgWoLpv7.mjs +172 -0
  11. package/dist/{knowledge-extractor-b9bel965.mjs → knowledge-extractor-DTO74tU1.mjs} +6 -6
  12. package/dist/living-spec-jepeb0NQ.mjs +218 -0
  13. package/dist/{post-tool-DdovyPpK.mjs → post-tool-tKu9d-gX.mjs} +19 -10
  14. package/dist/{pre-compact-CEwj7z9W.mjs → pre-compact-BmW-T36K.mjs} +7 -6
  15. package/dist/{pre-tool-MD1OPzGy.mjs → pre-tool-DEFSi2eU.mjs} +3 -3
  16. package/dist/{review-gate-Cqj-CmHP.mjs → review-gate-IIPdo-3r.mjs} +2 -2
  17. package/dist/{server-BPnToh8m.mjs → server-B0LsUetd.mjs} +11 -10
  18. package/dist/{server-BwAuDMm3.mjs → server-BaPbUbPa.mjs} +6 -5
  19. package/dist/{session-start-CxYp9xkq.mjs → session-start-CbBbiQR5.mjs} +9 -6
  20. package/dist/{state-C0v-bfFb.mjs → state-Dqi1EiD0.mjs} +19 -1
  21. package/dist/{stop-Cq9sP57g.mjs → stop-ByE7qdsR.mjs} +9 -4
  22. package/dist/{knowledge-kk6LxnnT.mjs → types-DFsKNXVY.mjs} +1 -171
  23. package/dist/{user-prompt-uwxFPXu1.mjs → user-prompt-E1Y0wuSR.mjs} +15 -8
  24. package/dist/{vectors-RRTv0qO0.mjs → vectors-DtWMZUgk.mjs} +1 -1
  25. package/package.json +1 -1
  26. /package/dist/{embedder-CuUqZpze.mjs → embedder-CFkDPOku.mjs} +0 -0
  27. /package/dist/{project-CpgK3fwQ.mjs → project-Cz-yOhrW.mjs} +0 -0
  28. /package/dist/{store-DenWQkPw.mjs → store-D4fokoGA.mjs} +0 -0
package/README.ja.md CHANGED
@@ -135,7 +135,7 @@ npm update -g claude-alfred # CLI、hooks、MCP サーバー、ダッシ
135
135
  |----------|------|
136
136
  | SessionStart | 仕様コンテキスト復元 + ナレッジ同期 + 1%ルール(スキル発動促進)+ 成熟度適応 |
137
137
  | PreCompact | スナップショット保存 + 決定抽出 + エピック進捗同期 + 調査パターン検出 |
138
- | UserPromptSubmit | セマンティック検索 + スキルナッジ + **spec承認ゲート**(未承認 M/L/XL に DIRECTIVE) |
138
+ | UserPromptSubmit | セマンティック検索 + スキルナッジ + **spec承認ゲート**(未承認 M/L/XL に DIRECTIVE)+ **並列開発ガード**(別タスク検出時に WARNING) |
139
139
  | PostToolUse | エラー検出 + Next Steps 自動チェック + ドリフト検出 + コミット時決定保存 |
140
140
  | **PreToolUse** | **3層 enforcement**: (1) review gate(spec/wave レビュー完了まで)、(2) intent guard(spec なし実装ブロック)、(3) approval gate(未承認 M/L/XL)。`.alfred/` 編集は常に許可 |
141
141
  | **Stop** | review gate → ブロック。その他 → コンテキストリマインド(ブロックなし) |
@@ -256,7 +256,7 @@ alfred dashboard --url-only # URLだけ出力
256
256
  Hook(見えない)
257
257
  |-- SessionStart -> コンテキスト復元、1%ルール、成熟度適応
258
258
  |-- PreCompact -> スナップショット保存、決定抽出、エピック進捗
259
- |-- UserPromptSubmit -> ベクトル検索 + FTS5 + スキルナッジ + spec承認チェック
259
+ |-- UserPromptSubmit -> ベクトル検索 + FTS5 + スキルナッジ + spec承認チェック + 並列開発ガード
260
260
  |-- PostToolUse -> エラー検出、Next Steps自動チェック、ドリフト検出
261
261
  |-- PreToolUse -> review gate + intent guard + approval gate(3層 enforcement)
262
262
  |-- Stop -> review gate ブロック + コンテキストリマインド(非ブロック)
package/README.md CHANGED
@@ -135,7 +135,7 @@ Run automatically. You don't touch these.
135
135
  |-------|-------------|
136
136
  | SessionStart | Restores spec context, syncs knowledge index, adapts injection depth to project maturity, 1% rule skill activation |
137
137
  | PreCompact | Extracts decisions, saves chapter snapshots, syncs epic progress, detects research patterns |
138
- | UserPromptSubmit | Semantic search + file context boost + **skill nudge** + **spec approval gate** (blocks implement intent on unapproved M/L/XL specs) |
138
+ | UserPromptSubmit | Semantic search + file context boost + **skill nudge** + **spec approval gate** (blocks implement intent on unapproved M/L/XL specs) + **parallel dev guard** (WARNING when active spec exists but new task detected) |
139
139
  | PostToolUse | Detects Bash errors + searches memory. After commits: spec drift detection + auto-save decisions. Edit/Write: auto-check Next Steps progress |
140
140
  | **PreToolUse** | **Three-layer enforcement**: (1) review-gate blocks until spec/wave review done, (2) intent guard blocks implementation without a spec, (3) approval gate blocks unapproved M/L/XL. `.alfred/` edits always allowed |
141
141
  | **Stop** | Review gate → block. Other incomplete items → context reminder (no block) |
@@ -256,7 +256,7 @@ You
256
256
  Hooks (invisible)
257
257
  |-- SessionStart -> restore context, sync knowledge, 1% rule, adapt to project maturity
258
258
  |-- PreCompact -> save snapshots, extract decisions, epic progress
259
- |-- UserPromptSubmit -> vector search + FTS5 + skill nudge + spec approval check
259
+ |-- UserPromptSubmit -> vector search + FTS5 + skill nudge + spec approval check + parallel dev guard
260
260
  |-- PostToolUse -> detect errors, auto-check Next Steps, drift detection
261
261
  |-- PreToolUse -> review-gate + intent guard + approval gate (3-layer enforcement)
262
262
  |-- Stop -> review-gate block + context reminders (non-blocking)
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { C as rootDir } from "./knowledge-kk6LxnnT.mjs";
2
+ import { u as rootDir } from "./types-DFsKNXVY.mjs";
3
3
  import { appendFileSync, mkdirSync } from "node:fs";
4
4
  import { join } from "node:path";
5
5
  //#region src/spec/audit.ts
package/dist/cli.mjs CHANGED
@@ -369,9 +369,9 @@ const main = defineCommand({
369
369
  serve: defineCommand({
370
370
  meta: { description: "Start MCP server (stdio)" },
371
371
  async run() {
372
- const { Store } = await import("./store-DenWQkPw.mjs");
373
- const { Embedder } = await import("./embedder-CuUqZpze.mjs");
374
- const { serveMCP } = await import("./server-BPnToh8m.mjs");
372
+ const { Store } = await import("./store-D4fokoGA.mjs");
373
+ const { Embedder } = await import("./embedder-CFkDPOku.mjs");
374
+ const { serveMCP } = await import("./server-B0LsUetd.mjs");
375
375
  const store = Store.openDefault();
376
376
  let emb = null;
377
377
  try {
@@ -397,9 +397,9 @@ const main = defineCommand({
397
397
  }
398
398
  },
399
399
  async run({ args }) {
400
- const { Store } = await import("./store-DenWQkPw.mjs");
401
- const { Embedder } = await import("./embedder-CuUqZpze.mjs");
402
- const { startDashboard } = await import("./server-BwAuDMm3.mjs");
400
+ const { Store } = await import("./store-D4fokoGA.mjs");
401
+ const { Embedder } = await import("./embedder-CFkDPOku.mjs");
402
+ const { startDashboard } = await import("./server-BaPbUbPa.mjs");
403
403
  const projectPath = process.cwd();
404
404
  const store = Store.openDefault();
405
405
  let emb = null;
@@ -422,7 +422,7 @@ const main = defineCommand({
422
422
  description: "Event name"
423
423
  } },
424
424
  async run({ args }) {
425
- const { runHook } = await import("./dispatcher-DZUzmm1Y.mjs").then((n) => n.t);
425
+ const { runHook } = await import("./dispatcher-M-Rc8GYD.mjs").then((n) => n.t);
426
426
  await runHook(args.event);
427
427
  }
428
428
  }),
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { n as emitAdditionalContext } from "./dispatcher-DZUzmm1Y.mjs";
2
+ import { n as emitAdditionalContext } from "./dispatcher-M-Rc8GYD.mjs";
3
3
  //#region src/hooks/directives.ts
4
4
  const LEVEL_ORDER = {
5
5
  DIRECTIVE: 0,
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { k as __exportAll } from "./knowledge-kk6LxnnT.mjs";
2
+ import { g as __exportAll } from "./types-DFsKNXVY.mjs";
3
3
  import { resolve } from "node:path";
4
4
  //#region src/hooks/dispatcher.ts
5
5
  var dispatcher_exports = /* @__PURE__ */ __exportAll({
@@ -89,27 +89,27 @@ async function runHook(event) {
89
89
  }
90
90
  }
91
91
  async function handleSessionStart(ev, signal) {
92
- const { sessionStart } = await import("./session-start-CxYp9xkq.mjs");
92
+ const { sessionStart } = await import("./session-start-CbBbiQR5.mjs");
93
93
  await sessionStart(ev, signal);
94
94
  }
95
95
  async function handlePreCompact(ev, signal) {
96
- const { preCompact } = await import("./pre-compact-CEwj7z9W.mjs");
96
+ const { preCompact } = await import("./pre-compact-BmW-T36K.mjs");
97
97
  await preCompact(ev, signal);
98
98
  }
99
99
  async function handleUserPromptSubmit(ev, signal) {
100
- const { userPromptSubmit } = await import("./user-prompt-uwxFPXu1.mjs");
100
+ const { userPromptSubmit } = await import("./user-prompt-E1Y0wuSR.mjs");
101
101
  await userPromptSubmit(ev, signal);
102
102
  }
103
103
  async function handlePostToolUse(ev, signal) {
104
- const { postToolUse } = await import("./post-tool-DdovyPpK.mjs");
104
+ const { postToolUse } = await import("./post-tool-tKu9d-gX.mjs");
105
105
  await postToolUse(ev, signal);
106
106
  }
107
107
  async function handlePreToolUse(ev, _signal) {
108
- const { preToolUse } = await import("./pre-tool-MD1OPzGy.mjs");
108
+ const { preToolUse } = await import("./pre-tool-DEFSi2eU.mjs");
109
109
  await preToolUse(ev);
110
110
  }
111
111
  async function handleStop(ev, _signal) {
112
- const { stop } = await import("./stop-Cq9sP57g.mjs");
112
+ const { stop } = await import("./stop-ByE7qdsR.mjs");
113
113
  await stop(ev);
114
114
  }
115
115
  //#endregion
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { D as require_dist, h as VALID_SLUG } from "./knowledge-kk6LxnnT.mjs";
2
+ import { m as require_dist, n as VALID_SLUG } from "./types-DFsKNXVY.mjs";
3
3
  import { mkdirSync, readFileSync, readdirSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
4
4
  import { join } from "node:path";
5
5
  //#region src/epic/index.ts
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { d as searchKnowledgeKeyword, i as getKnowledgeByIDs, l as mapRow } from "./knowledge-kk6LxnnT.mjs";
3
- import { n as deserializeFloat32, t as cosineSimilarity } from "./vectors-RRTv0qO0.mjs";
2
+ import { d as searchKnowledgeKeyword, i as getKnowledgeByIDs, l as mapRow } from "./knowledge-BgWoLpv7.mjs";
3
+ import { n as deserializeFloat32, t as cosineSimilarity } from "./vectors-DtWMZUgk.mjs";
4
4
  //#region src/store/fts.ts
5
5
  function subTypeHalfLife(subType) {
6
6
  switch (subType) {
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
- import { c as incrementHitCount, d as searchKnowledgeKeyword, i as getKnowledgeByIDs } from "./knowledge-kk6LxnnT.mjs";
3
- import { r as vectorSearchKnowledge } from "./vectors-RRTv0qO0.mjs";
4
- import { i as subTypeHalfLife, n as searchKnowledgeFTS, r as subTypeBoost } from "./fts-Ck8wjOIE.mjs";
2
+ import { c as incrementHitCount, d as searchKnowledgeKeyword, i as getKnowledgeByIDs } from "./knowledge-BgWoLpv7.mjs";
3
+ import { r as vectorSearchKnowledge } from "./vectors-DtWMZUgk.mjs";
4
+ import { i as subTypeHalfLife, n as searchKnowledgeFTS, r as subTypeBoost } from "./fts-Buk8fkl1.mjs";
5
5
  //#region src/mcp/helpers.ts
6
6
  const RECENCY_FLOOR = .5;
7
7
  function truncate(s, maxLen) {
@@ -0,0 +1,172 @@
1
+ #!/usr/bin/env node
2
+ import { createHash } from "node:crypto";
3
+ //#region src/store/knowledge.ts
4
+ function contentHash(content) {
5
+ return createHash("sha256").update(content).digest("hex");
6
+ }
7
+ function upsertKnowledge(store, row) {
8
+ const now = (/* @__PURE__ */ new Date()).toISOString();
9
+ if (!row.createdAt) row.createdAt = now;
10
+ row.updatedAt = now;
11
+ row.contentHash = contentHash(row.content);
12
+ const existing = store.db.prepare("SELECT id, content_hash FROM knowledge_index WHERE project_remote = ? AND project_path = ? AND file_path = ?").get(row.projectRemote, row.projectPath, row.filePath);
13
+ if (existing && existing.content_hash === row.contentHash) {
14
+ row.id = existing.id;
15
+ return {
16
+ id: existing.id,
17
+ changed: false
18
+ };
19
+ }
20
+ const result = store.db.prepare(`
21
+ INSERT INTO knowledge_index
22
+ (file_path, content_hash, title, content, sub_type,
23
+ project_remote, project_path, project_name, branch,
24
+ created_at, updated_at, hit_count, last_accessed, enabled)
25
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, '', 1)
26
+ ON CONFLICT(project_remote, project_path, file_path) DO UPDATE SET
27
+ content_hash = excluded.content_hash,
28
+ title = excluded.title,
29
+ content = excluded.content,
30
+ sub_type = excluded.sub_type,
31
+ project_name = excluded.project_name,
32
+ branch = excluded.branch,
33
+ updated_at = excluded.updated_at
34
+ `).run(row.filePath, row.contentHash, row.title, row.content, row.subType, row.projectRemote, row.projectPath, row.projectName, row.branch, row.createdAt, row.updatedAt);
35
+ const id = Number(result.lastInsertRowid);
36
+ row.id = id;
37
+ return {
38
+ id,
39
+ changed: true
40
+ };
41
+ }
42
+ function getKnowledgeByID(store, id) {
43
+ const row = store.db.prepare(`
44
+ SELECT id, file_path, content_hash, title, content, sub_type,
45
+ project_remote, project_path, project_name, branch,
46
+ created_at, updated_at, hit_count, last_accessed, enabled
47
+ FROM knowledge_index WHERE id = ?
48
+ `).get(id);
49
+ return row ? mapRow(row) : void 0;
50
+ }
51
+ function getKnowledgeByIDs(store, ids) {
52
+ if (ids.length === 0) return [];
53
+ const placeholders = ids.map(() => "?").join(",");
54
+ return store.db.prepare(`
55
+ SELECT id, file_path, content_hash, title, content, sub_type,
56
+ project_remote, project_path, project_name, branch,
57
+ created_at, updated_at, hit_count, last_accessed, enabled
58
+ FROM knowledge_index WHERE id IN (${placeholders})
59
+ `).all(...ids).map(mapRow);
60
+ }
61
+ function setKnowledgeEnabled(store, id, enabled) {
62
+ store.db.prepare("UPDATE knowledge_index SET enabled = ? WHERE id = ?").run(enabled ? 1 : 0, id);
63
+ }
64
+ function incrementHitCount(store, ids) {
65
+ if (ids.length === 0) return;
66
+ const now = (/* @__PURE__ */ new Date()).toISOString();
67
+ const placeholders = ids.map(() => "?").join(",");
68
+ store.db.prepare(`UPDATE knowledge_index SET hit_count = hit_count + 1, last_accessed = ?
69
+ WHERE id IN (${placeholders})`).run(now, ...ids);
70
+ }
71
+ function promoteSubType(store, id, newSubType) {
72
+ const now = (/* @__PURE__ */ new Date()).toISOString();
73
+ if (store.db.prepare("UPDATE knowledge_index SET sub_type = ?, updated_at = ? WHERE id = ? AND enabled = 1").run(newSubType, now, id).changes === 0) throw new Error(`store: promote sub_type: knowledge ${id} not found or disabled`);
74
+ }
75
+ function getPromotionCandidates(store) {
76
+ return store.db.prepare(`
77
+ SELECT id, file_path, content_hash, title, content, sub_type,
78
+ project_remote, project_path, project_name, branch,
79
+ created_at, updated_at, hit_count, last_accessed, enabled
80
+ FROM knowledge_index
81
+ WHERE enabled = 1
82
+ AND (sub_type = 'pattern' AND hit_count >= 15)
83
+ ORDER BY hit_count DESC
84
+ `).all().map(mapRow);
85
+ }
86
+ function getKnowledgeStats(store) {
87
+ const agg = store.db.prepare("SELECT COUNT(*) as total, COALESCE(AVG(hit_count), 0) as avg_hits FROM knowledge_index WHERE enabled = 1").get();
88
+ const bySubType = {};
89
+ const subtypeRows = store.db.prepare("SELECT sub_type, COUNT(*) as cnt FROM knowledge_index WHERE enabled = 1 GROUP BY sub_type").all();
90
+ for (const r of subtypeRows) bySubType[r.sub_type] = r.cnt;
91
+ const topRows = store.db.prepare(`
92
+ SELECT id, file_path, content_hash, title, content, sub_type,
93
+ project_remote, project_path, project_name, branch,
94
+ created_at, updated_at, hit_count, last_accessed, enabled
95
+ FROM knowledge_index WHERE enabled = 1
96
+ ORDER BY hit_count DESC LIMIT 5
97
+ `).all();
98
+ return {
99
+ total: agg?.total ?? 0,
100
+ avgHitCount: agg?.avg_hits ?? 0,
101
+ bySubType,
102
+ topAccessed: topRows.map(mapRow)
103
+ };
104
+ }
105
+ function searchKnowledgeKeyword(store, query, limit) {
106
+ const escaped = escapeLIKEContains(query);
107
+ return store.db.prepare(`
108
+ SELECT id, file_path, content_hash, title, content, sub_type,
109
+ project_remote, project_path, project_name, branch,
110
+ created_at, updated_at, hit_count, last_accessed, enabled
111
+ FROM knowledge_index
112
+ WHERE enabled = 1 AND (content LIKE ? ESCAPE '\\' OR title LIKE ? ESCAPE '\\')
113
+ ORDER BY hit_count DESC LIMIT ?
114
+ `).all(escaped, escaped, limit).map(mapRow);
115
+ }
116
+ function getRecentDecisions(store, projectRemote, projectPath, sinceISO, limit) {
117
+ return store.db.prepare(`
118
+ SELECT title, content, created_at FROM knowledge_index
119
+ WHERE sub_type = 'decision'
120
+ AND project_remote = ? AND project_path = ?
121
+ AND created_at > ? AND enabled = 1
122
+ ORDER BY created_at DESC LIMIT ?
123
+ `).all(projectRemote, projectPath, sinceISO, limit).map((r) => ({
124
+ title: r.title,
125
+ content: r.content,
126
+ createdAt: r.created_at
127
+ }));
128
+ }
129
+ function deleteOrphanKnowledge(store, projectRemote, projectPath, branch, validFilePaths) {
130
+ const rows = store.db.prepare("SELECT id, file_path FROM knowledge_index WHERE project_remote = ? AND project_path = ? AND branch = ?").all(projectRemote, projectPath, branch);
131
+ let deleted = 0;
132
+ const delEmbed = store.db.prepare("DELETE FROM embeddings WHERE source = 'knowledge' AND source_id = ?");
133
+ const delKnowledge = store.db.prepare("DELETE FROM knowledge_index WHERE id = ?");
134
+ store.db.transaction(() => {
135
+ for (const row of rows) if (!validFilePaths.has(row.file_path)) {
136
+ delEmbed.run(row.id);
137
+ delKnowledge.run(row.id);
138
+ deleted++;
139
+ }
140
+ })();
141
+ return deleted;
142
+ }
143
+ function countKnowledge(store, projectRemote, projectPath) {
144
+ return store.db.prepare("SELECT COUNT(*) as cnt FROM knowledge_index WHERE project_remote = ? AND project_path = ? AND enabled = 1").get(projectRemote, projectPath).cnt;
145
+ }
146
+ function escapeLIKEContains(s) {
147
+ s = s.replaceAll("\\", "\\\\");
148
+ s = s.replaceAll("%", "\\%");
149
+ s = s.replaceAll("_", "\\_");
150
+ return `%${s}%`;
151
+ }
152
+ function mapRow(r) {
153
+ return {
154
+ id: r.id,
155
+ filePath: r.file_path,
156
+ contentHash: r.content_hash,
157
+ title: r.title,
158
+ content: r.content,
159
+ subType: r.sub_type,
160
+ projectRemote: r.project_remote,
161
+ projectPath: r.project_path,
162
+ projectName: r.project_name,
163
+ branch: r.branch,
164
+ createdAt: r.created_at,
165
+ updatedAt: r.updated_at,
166
+ hitCount: r.hit_count,
167
+ lastAccessed: r.last_accessed,
168
+ enabled: r.enabled === 1
169
+ };
170
+ }
171
+ //#endregion
172
+ export { getKnowledgeStats as a, incrementHitCount as c, searchKnowledgeKeyword as d, setKnowledgeEnabled as f, getKnowledgeByIDs as i, mapRow as l, deleteOrphanKnowledge as n, getPromotionCandidates as o, upsertKnowledge as p, getKnowledgeByID as r, getRecentDecisions as s, countKnowledge as t, promoteSubType as u };
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
- import { a as getKnowledgeStats, o as getPromotionCandidates, p as upsertKnowledge, r as getKnowledgeByID, u as promoteSubType } from "./knowledge-kk6LxnnT.mjs";
3
- import { t as appendAudit } from "./audit-09Rf9Awg.mjs";
4
- import { t as detectKnowledgeConflicts } from "./fts-Ck8wjOIE.mjs";
5
- import { t as detectProject } from "./project-CpgK3fwQ.mjs";
6
- import { n as trackHitCounts, r as truncate, t as searchPipeline } from "./helpers-rMTa9O_h.mjs";
2
+ import { t as appendAudit } from "./audit-g6phLGMg.mjs";
3
+ import { a as getKnowledgeStats, o as getPromotionCandidates, p as upsertKnowledge, r as getKnowledgeByID, u as promoteSubType } from "./knowledge-BgWoLpv7.mjs";
4
+ import { t as detectKnowledgeConflicts } from "./fts-Buk8fkl1.mjs";
5
+ import { t as detectProject } from "./project-Cz-yOhrW.mjs";
6
+ import { n as trackHitCounts, r as truncate, t as searchPipeline } from "./helpers-CvI9bVCq.mjs";
7
7
  import { mkdirSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
8
8
  import { join } from "node:path";
9
9
  import { createHash } from "node:crypto";
@@ -245,7 +245,7 @@ async function ledgerSave(store, emb, params) {
245
245
  const model = emb.model;
246
246
  const embText = `${params.title} ${params.context_text ?? ""} ${params.decision ?? params.pattern ?? params.text ?? ""}`;
247
247
  emb.embedForStorage(embText).then(async (vec) => {
248
- const { insertEmbedding } = await import("./vectors-RRTv0qO0.mjs").then((n) => n.i);
248
+ const { insertEmbedding } = await import("./vectors-DtWMZUgk.mjs").then((n) => n.i);
249
249
  insertEmbedding(store, "knowledge", dbId, model, vec);
250
250
  }).catch((err) => {
251
251
  console.error(`[alfred] embedding failed for ${dbId}: ${err}`);
@@ -0,0 +1,218 @@
1
+ #!/usr/bin/env node
2
+ import { o as readActive, t as SpecDir } from "./types-DFsKNXVY.mjs";
3
+ import { t as appendAudit } from "./audit-g6phLGMg.mjs";
4
+ import { basename, dirname, extname } from "node:path";
5
+ import { execSync } from "node:child_process";
6
+ //#region src/hooks/lang-filter.ts
7
+ /**
8
+ * Language filter for Living Spec auto-append.
9
+ * Determines which source files should be tracked in design.md.
10
+ */
11
+ const LANGUAGES = [
12
+ {
13
+ extensions: [
14
+ ".js",
15
+ ".jsx",
16
+ ".ts",
17
+ ".tsx",
18
+ ".mjs",
19
+ ".mts"
20
+ ],
21
+ excludeSuffixes: [
22
+ ".test.ts",
23
+ ".spec.ts",
24
+ ".test.js",
25
+ ".spec.js",
26
+ ".test.tsx",
27
+ ".spec.tsx",
28
+ ".test.mts",
29
+ ".spec.mts",
30
+ ".d.ts",
31
+ ".min.js",
32
+ ".bundle.js"
33
+ ],
34
+ excludePrefixes: []
35
+ },
36
+ {
37
+ extensions: [".py"],
38
+ excludeSuffixes: [
39
+ "_test.py",
40
+ "_pb2.py",
41
+ "_pb2_grpc.py"
42
+ ],
43
+ excludePrefixes: ["test_", "conftest"]
44
+ },
45
+ {
46
+ extensions: [".go"],
47
+ excludeSuffixes: [
48
+ "_test.go",
49
+ "_gen.go",
50
+ ".pb.go",
51
+ "_mock.go",
52
+ "_string.go"
53
+ ],
54
+ excludePrefixes: []
55
+ },
56
+ {
57
+ extensions: [".rb"],
58
+ excludeSuffixes: ["_test.rb", "_spec.rb"],
59
+ excludePrefixes: []
60
+ }
61
+ ];
62
+ const DIR_EXCLUSIONS = [
63
+ "vendor/",
64
+ "node_modules/",
65
+ ".alfred/",
66
+ "dist/",
67
+ "build/",
68
+ "__pycache__/",
69
+ ".venv/",
70
+ "plugin/"
71
+ ];
72
+ /** Check if a source file should be auto-appended to design.md. */
73
+ function shouldAutoAppend(filePath) {
74
+ for (const dir of DIR_EXCLUSIONS) if (filePath.startsWith(dir) || filePath.includes(`/${dir}`)) return false;
75
+ const ext = extname(filePath);
76
+ const name = basename(filePath);
77
+ for (const lang of LANGUAGES) {
78
+ if (!lang.extensions.includes(ext)) continue;
79
+ for (const suffix of lang.excludeSuffixes) if (filePath.endsWith(suffix)) return false;
80
+ for (const prefix of lang.excludePrefixes) if (name.startsWith(prefix)) return false;
81
+ return true;
82
+ }
83
+ return false;
84
+ }
85
+ //#endregion
86
+ //#region src/hooks/living-spec.ts
87
+ /**
88
+ * Living Spec auto-append: after git commit, detect changed source files
89
+ * and append them to the matching component section in design.md.
90
+ *
91
+ * Multi-language: JS/TS, Python, Go, Ruby.
92
+ * Fail-open: all errors silently ignored.
93
+ */
94
+ /**
95
+ * Run Living Spec auto-append after a successful git commit.
96
+ * Returns set of auto-appended file paths (for drift detection exclusion).
97
+ */
98
+ function handleLivingSpec(cwd) {
99
+ const appended = /* @__PURE__ */ new Set();
100
+ try {
101
+ const slug = readActive(cwd);
102
+ const sd = new SpecDir(cwd, slug);
103
+ let designContent;
104
+ try {
105
+ designContent = sd.readFile("design.md");
106
+ } catch {
107
+ return appended;
108
+ }
109
+ const changedFiles = extractChangedFiles(cwd);
110
+ if (changedFiles.length === 0) return appended;
111
+ const sourceFiles = changedFiles.filter(shouldAutoAppend);
112
+ if (sourceFiles.length === 0) return appended;
113
+ const componentMap = parseDesignFileRefs(designContent);
114
+ if (componentMap.size === 0) return appended;
115
+ let updatedContent = designContent;
116
+ const appendedComponents = [];
117
+ for (const file of sourceFiles) {
118
+ if (designContent.includes(`\`${file}\``)) continue;
119
+ const component = matchComponent(file, componentMap);
120
+ if (!component) continue;
121
+ const result = appendFileToComponent(updatedContent, component, file);
122
+ if (result) {
123
+ updatedContent = result;
124
+ appended.add(file);
125
+ appendedComponents.push(`${component}:${file}`);
126
+ }
127
+ }
128
+ if (appended.size > 0) {
129
+ sd.writeFile("design.md", updatedContent);
130
+ appendAudit(cwd, {
131
+ action: "living-spec.update",
132
+ target: slug,
133
+ detail: JSON.stringify({
134
+ files: [...appended],
135
+ components: appendedComponents
136
+ })
137
+ });
138
+ }
139
+ } catch {}
140
+ return appended;
141
+ }
142
+ /**
143
+ * Extract changed files from the last git commit.
144
+ * 500ms timeout, fail-open.
145
+ */
146
+ function extractChangedFiles(cwd) {
147
+ try {
148
+ return execSync("git diff --name-only HEAD~1", {
149
+ cwd,
150
+ timeout: 500,
151
+ encoding: "utf-8",
152
+ stdio: [
153
+ "ignore",
154
+ "pipe",
155
+ "ignore"
156
+ ]
157
+ }).split("\n").map((f) => f.trim()).filter(Boolean);
158
+ } catch {
159
+ return [];
160
+ }
161
+ }
162
+ /**
163
+ * Parse design.md for component → file references.
164
+ * Looks for `### Component: Name` headings with `**File**: \`path\`` lines.
165
+ */
166
+ function parseDesignFileRefs(content) {
167
+ const map = /* @__PURE__ */ new Map();
168
+ let currentComponent = "";
169
+ for (const line of content.split("\n")) {
170
+ const compMatch = line.match(/^###\s+(?:Component:\s*)?(.+)/);
171
+ if (compMatch) {
172
+ currentComponent = compMatch[1].trim();
173
+ if (!map.has(currentComponent)) map.set(currentComponent, []);
174
+ continue;
175
+ }
176
+ if (currentComponent) {
177
+ const fileMatch = line.match(/\*\*File\*\*:\s*`([^`]+)`/);
178
+ if (fileMatch) map.get(currentComponent).push(fileMatch[1]);
179
+ }
180
+ }
181
+ return map;
182
+ }
183
+ /**
184
+ * Match a file to a component by comparing directory paths.
185
+ * Returns component name or null.
186
+ */
187
+ function matchComponent(filePath, componentMap) {
188
+ const fileDir = dirname(filePath);
189
+ for (const [component, files] of componentMap) for (const existing of files) if (dirname(existing) === fileDir) return component;
190
+ return null;
191
+ }
192
+ /**
193
+ * Append a file reference after the last **File**: line in a component section.
194
+ * Returns updated content or null if unable to insert.
195
+ */
196
+ function appendFileToComponent(content, component, filePath) {
197
+ const lines = content.split("\n");
198
+ const newLine = `- **File**: \`${filePath}\` <!-- auto-added: ${(/* @__PURE__ */ new Date()).toISOString()} -->`;
199
+ let componentIdx = -1;
200
+ for (let i = 0; i < lines.length; i++) {
201
+ const line = lines[i];
202
+ if (line.match(/^###\s+/) && (line.includes(`Component: ${component}`) || line.includes(component))) {
203
+ componentIdx = i;
204
+ break;
205
+ }
206
+ }
207
+ if (componentIdx === -1) return null;
208
+ let lastFileIdx = -1;
209
+ for (let i = componentIdx + 1; i < lines.length; i++) {
210
+ if (lines[i].match(/^##/)) break;
211
+ if (lines[i].includes("**File**:")) lastFileIdx = i;
212
+ }
213
+ if (lastFileIdx === -1) return null;
214
+ lines.splice(lastFileIdx + 1, 0, newLine);
215
+ return lines.join("\n");
216
+ }
217
+ //#endregion
218
+ export { handleLivingSpec };
@@ -1,14 +1,15 @@
1
1
  #!/usr/bin/env node
2
- import { b as readActiveState, m as SpecDir, o as getPromotionCandidates, p as upsertKnowledge, u as promoteSubType, y as readActive } from "./knowledge-kk6LxnnT.mjs";
3
- import "./audit-09Rf9Awg.mjs";
4
- import { n as searchKnowledgeFTS, t as detectKnowledgeConflicts } from "./fts-Ck8wjOIE.mjs";
5
- import { t as detectProject } from "./project-CpgK3fwQ.mjs";
6
- import { i as notifyUser, r as extractSection } from "./dispatcher-DZUzmm1Y.mjs";
7
- import { r as truncate } from "./helpers-rMTa9O_h.mjs";
8
- import { n as extractReviewFindings, r as saveKnowledgeEntries } from "./knowledge-extractor-b9bel965.mjs";
9
- import { openDefaultCached } from "./store-DenWQkPw.mjs";
10
- import { t as emitDirectives } from "./directives-Bm6w4JNk.mjs";
11
- import { i as readStateText, s as writeStateText } from "./state-C0v-bfFb.mjs";
2
+ import { o as readActive, s as readActiveState, t as SpecDir } from "./types-DFsKNXVY.mjs";
3
+ import "./audit-g6phLGMg.mjs";
4
+ import { o as getPromotionCandidates, p as upsertKnowledge, u as promoteSubType } from "./knowledge-BgWoLpv7.mjs";
5
+ import { n as searchKnowledgeFTS, t as detectKnowledgeConflicts } from "./fts-Buk8fkl1.mjs";
6
+ import { t as detectProject } from "./project-Cz-yOhrW.mjs";
7
+ import { i as notifyUser, r as extractSection } from "./dispatcher-M-Rc8GYD.mjs";
8
+ import { r as truncate } from "./helpers-CvI9bVCq.mjs";
9
+ import { n as extractReviewFindings, r as saveKnowledgeEntries } from "./knowledge-extractor-DTO74tU1.mjs";
10
+ import { openDefaultCached } from "./store-D4fokoGA.mjs";
11
+ import { t as emitDirectives } from "./directives-BnbWxhap.mjs";
12
+ import { a as readStateText, n as addWorkedSlug, u as writeStateText } from "./state-Dqi1EiD0.mjs";
12
13
  //#region src/hooks/post-tool.ts
13
14
  function readExploreCount(cwd) {
14
15
  return parseInt(readStateText(cwd, "explore-count", "0"), 10) || 0;
@@ -40,6 +41,10 @@ async function postToolUse(ev, signal) {
40
41
  const input = ev.tool_input;
41
42
  const filePath = typeof input.file_path === "string" ? input.file_path : "";
42
43
  if (filePath) autoCheckNextSteps(ev.cwd, filePath);
44
+ try {
45
+ const slug = readActive(ev.cwd);
46
+ addWorkedSlug(ev.cwd, slug);
47
+ } catch {}
43
48
  }
44
49
  if ([
45
50
  "Edit",
@@ -66,6 +71,10 @@ async function handleBashResult(ev, items, signal) {
66
71
  const commandStr = typeof ev.tool_input === "object" && ev.tool_input !== null ? ev.tool_input.command ?? "" : "";
67
72
  autoCheckNextSteps(ev.cwd, `${stdout}\n${commandStr}`);
68
73
  if (isGitCommit(stdout) && !signal.aborted) {
74
+ try {
75
+ const { handleLivingSpec } = await import("./living-spec-jepeb0NQ.mjs");
76
+ handleLivingSpec(ev.cwd);
77
+ } catch {}
69
78
  await checkKnowledgeConflicts(items);
70
79
  saveKnowledgeOnCommit(ev.cwd);
71
80
  }
@@ -1,10 +1,11 @@
1
1
  #!/usr/bin/env node
2
- import { S as reviewStatusFor, T as verifyReviewFile, b as readActiveState, g as completeTask, m as SpecDir, p as upsertKnowledge, y as readActive } from "./knowledge-kk6LxnnT.mjs";
3
- import { o as syncTaskStatus } from "./epic-Cde2RANp.mjs";
4
- import { t as appendAudit } from "./audit-09Rf9Awg.mjs";
5
- import { t as detectProject } from "./project-CpgK3fwQ.mjs";
6
- import { i as notifyUser } from "./dispatcher-DZUzmm1Y.mjs";
7
- import { openDefaultCached } from "./store-DenWQkPw.mjs";
2
+ import { f as verifyReviewFile, l as reviewStatusFor, o as readActive, r as completeTask, s as readActiveState, t as SpecDir } from "./types-DFsKNXVY.mjs";
3
+ import { o as syncTaskStatus } from "./epic-D9ksT1k7.mjs";
4
+ import { t as appendAudit } from "./audit-g6phLGMg.mjs";
5
+ import { p as upsertKnowledge } from "./knowledge-BgWoLpv7.mjs";
6
+ import { t as detectProject } from "./project-Cz-yOhrW.mjs";
7
+ import { i as notifyUser } from "./dispatcher-M-Rc8GYD.mjs";
8
+ import { openDefaultCached } from "./store-D4fokoGA.mjs";
8
9
  import { readFileSync, writeFileSync } from "node:fs";
9
10
  import { join } from "node:path";
10
11
  //#region src/hooks/pre-compact.ts
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
- import "./dispatcher-DZUzmm1Y.mjs";
3
- import { n as readLastIntent, t as IMPLEMENT_INTENTS } from "./state-C0v-bfFb.mjs";
4
- import { l as isSpecFilePath, n as isGateActive, s as denyTool, u as tryReadActiveSpec } from "./review-gate-Cqj-CmHP.mjs";
2
+ import "./dispatcher-M-Rc8GYD.mjs";
3
+ import { r as readLastIntent, t as IMPLEMENT_INTENTS } from "./state-Dqi1EiD0.mjs";
4
+ import { l as isSpecFilePath, n as isGateActive, s as denyTool, u as tryReadActiveSpec } from "./review-gate-IIPdo-3r.mjs";
5
5
  import { existsSync } from "node:fs";
6
6
  import { join } from "node:path";
7
7
  //#region src/hooks/pre-tool.ts
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { r as extractSection } from "./dispatcher-DZUzmm1Y.mjs";
3
- import { o as writeStateJSON, r as readStateJSON } from "./state-C0v-bfFb.mjs";
2
+ import { r as extractSection } from "./dispatcher-M-Rc8GYD.mjs";
3
+ import { i as readStateJSON, l as writeStateJSON } from "./state-Dqi1EiD0.mjs";
4
4
  import { readFileSync } from "node:fs";
5
5
  import { join, resolve } from "node:path";
6
6
  //#region src/hooks/spec-guard.ts
@@ -1,14 +1,15 @@
1
1
  #!/usr/bin/env node
2
- import { A as __toESM, E as writeActiveState, O as __commonJSMin, S as reviewStatusFor, T as verifyReviewFile, _ as detectSize, b as readActiveState, g as completeTask, h as VALID_SLUG, i as getKnowledgeByIDs, m as SpecDir, v as filesForSize, w as switchActive, x as removeTask, y as readActive } from "./knowledge-kk6LxnnT.mjs";
3
- import { a as removeEpic, c as unlinkTaskFromAllEpics, i as nextActionable, n as initEpic, o as syncTaskStatus, r as listAllEpics, s as topologicalOrder, t as EpicDir } from "./epic-Cde2RANp.mjs";
4
- import { t as appendAudit } from "./audit-09Rf9Awg.mjs";
5
- import { r as vectorSearchKnowledge } from "./vectors-RRTv0qO0.mjs";
6
- import { n as searchKnowledgeFTS, r as subTypeBoost } from "./fts-Ck8wjOIE.mjs";
7
- import "./dispatcher-DZUzmm1Y.mjs";
8
- import { r as truncate } from "./helpers-rMTa9O_h.mjs";
9
- import { i as handleLedger, r as saveKnowledgeEntries, t as extractPatterns } from "./knowledge-extractor-b9bel965.mjs";
10
- import "./state-C0v-bfFb.mjs";
11
- import { i as writeReviewGate, r as readReviewGate, t as clearReviewGate } from "./review-gate-Cqj-CmHP.mjs";
2
+ import { _ as __toESM, a as filesForSize, c as removeTask, d as switchActive, f as verifyReviewFile, h as __commonJSMin, i as detectSize, l as reviewStatusFor, n as VALID_SLUG, o as readActive, p as writeActiveState, r as completeTask, s as readActiveState, t as SpecDir } from "./types-DFsKNXVY.mjs";
3
+ import { a as removeEpic, c as unlinkTaskFromAllEpics, i as nextActionable, n as initEpic, o as syncTaskStatus, r as listAllEpics, s as topologicalOrder, t as EpicDir } from "./epic-D9ksT1k7.mjs";
4
+ import { t as appendAudit } from "./audit-g6phLGMg.mjs";
5
+ import { i as getKnowledgeByIDs } from "./knowledge-BgWoLpv7.mjs";
6
+ import { r as vectorSearchKnowledge } from "./vectors-DtWMZUgk.mjs";
7
+ import { n as searchKnowledgeFTS, r as subTypeBoost } from "./fts-Buk8fkl1.mjs";
8
+ import "./dispatcher-M-Rc8GYD.mjs";
9
+ import { r as truncate } from "./helpers-CvI9bVCq.mjs";
10
+ import { i as handleLedger, r as saveKnowledgeEntries, t as extractPatterns } from "./knowledge-extractor-DTO74tU1.mjs";
11
+ import "./state-Dqi1EiD0.mjs";
12
+ import { i as writeReviewGate, r as readReviewGate, t as clearReviewGate } from "./review-gate-IIPdo-3r.mjs";
12
13
  import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
13
14
  import { join, resolve } from "node:path";
14
15
  import process$1 from "node:process";
@@ -1,9 +1,10 @@
1
1
  #!/usr/bin/env node
2
- import { E as writeActiveState, T as verifyReviewFile, a as getKnowledgeStats, b as readActiveState, f as setKnowledgeEnabled, g as completeTask, h as VALID_SLUG, m as SpecDir, v as filesForSize } from "./knowledge-kk6LxnnT.mjs";
3
- import { r as listAllEpics } from "./epic-Cde2RANp.mjs";
4
- import { t as appendAudit } from "./audit-09Rf9Awg.mjs";
5
- import { n as searchKnowledgeFTS } from "./fts-Ck8wjOIE.mjs";
6
- import { t as detectProject } from "./project-CpgK3fwQ.mjs";
2
+ import { a as filesForSize, f as verifyReviewFile, n as VALID_SLUG, p as writeActiveState, r as completeTask, s as readActiveState, t as SpecDir } from "./types-DFsKNXVY.mjs";
3
+ import { r as listAllEpics } from "./epic-D9ksT1k7.mjs";
4
+ import { t as appendAudit } from "./audit-g6phLGMg.mjs";
5
+ import { a as getKnowledgeStats, f as setKnowledgeEnabled } from "./knowledge-BgWoLpv7.mjs";
6
+ import { n as searchKnowledgeFTS } from "./fts-Buk8fkl1.mjs";
7
+ import { t as detectProject } from "./project-Cz-yOhrW.mjs";
7
8
  import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
8
9
  import { join } from "node:path";
9
10
  import { fileURLToPath } from "node:url";
@@ -1,15 +1,18 @@
1
1
  #!/usr/bin/env node
2
- import { b as readActiveState, m as SpecDir, n as deleteOrphanKnowledge, p as upsertKnowledge, s as getRecentDecisions, t as countKnowledge, y as readActive } from "./knowledge-kk6LxnnT.mjs";
3
- import { t as detectProject } from "./project-CpgK3fwQ.mjs";
4
- import { i as notifyUser, r as extractSection } from "./dispatcher-DZUzmm1Y.mjs";
5
- import { r as truncate } from "./helpers-rMTa9O_h.mjs";
6
- import { openDefaultCached } from "./store-DenWQkPw.mjs";
7
- import { t as emitDirectives } from "./directives-Bm6w4JNk.mjs";
2
+ import { o as readActive, s as readActiveState, t as SpecDir } from "./types-DFsKNXVY.mjs";
3
+ import { n as deleteOrphanKnowledge, p as upsertKnowledge, s as getRecentDecisions, t as countKnowledge } from "./knowledge-BgWoLpv7.mjs";
4
+ import { t as detectProject } from "./project-Cz-yOhrW.mjs";
5
+ import { i as notifyUser, r as extractSection } from "./dispatcher-M-Rc8GYD.mjs";
6
+ import { r as truncate } from "./helpers-CvI9bVCq.mjs";
7
+ import { openDefaultCached } from "./store-D4fokoGA.mjs";
8
+ import { t as emitDirectives } from "./directives-BnbWxhap.mjs";
9
+ import { s as resetWorkedSlugs } from "./state-Dqi1EiD0.mjs";
8
10
  import { existsSync, readFileSync, readdirSync } from "node:fs";
9
11
  import { join } from "node:path";
10
12
  //#region src/hooks/session-start.ts
11
13
  async function sessionStart(ev, _signal) {
12
14
  if (!ev.cwd) return;
15
+ if (existsSync(join(ev.cwd, ".alfred"))) resetWorkedSlugs(ev.cwd);
13
16
  let store;
14
17
  try {
15
18
  store = openDefaultCached();
@@ -73,5 +73,23 @@ function readLastIntent(cwd) {
73
73
  if (Date.now() - data.timestamp > INTENT_EXPIRY_MS) return null;
74
74
  return data.intent;
75
75
  }
76
+ const WORKED_SLUGS_FILE = "worked-slugs.json";
77
+ /** Read the list of spec slugs worked on in this session. */
78
+ function readWorkedSlugs(cwd) {
79
+ const data = readStateJSON(cwd, WORKED_SLUGS_FILE, []);
80
+ return Array.isArray(data) ? data : [];
81
+ }
82
+ /** Add a slug to the worked-slugs list (deduplicates). */
83
+ function addWorkedSlug(cwd, slug) {
84
+ const slugs = readWorkedSlugs(cwd);
85
+ if (!slugs.includes(slug)) {
86
+ slugs.push(slug);
87
+ writeStateJSON(cwd, WORKED_SLUGS_FILE, slugs);
88
+ }
89
+ }
90
+ /** Reset worked-slugs at session start. */
91
+ function resetWorkedSlugs(cwd) {
92
+ writeStateJSON(cwd, WORKED_SLUGS_FILE, []);
93
+ }
76
94
  //#endregion
77
- export { writeLastIntent as a, readStateText as i, readLastIntent as n, writeStateJSON as o, readStateJSON as r, writeStateText as s, IMPLEMENT_INTENTS as t };
95
+ export { readStateText as a, writeLastIntent as c, readStateJSON as i, writeStateJSON as l, addWorkedSlug as n, readWorkedSlugs as o, readLastIntent as r, resetWorkedSlugs as s, IMPLEMENT_INTENTS as t, writeStateText as u };
@@ -1,12 +1,14 @@
1
1
  #!/usr/bin/env node
2
- import { n as emitAdditionalContext } from "./dispatcher-DZUzmm1Y.mjs";
3
- import "./state-C0v-bfFb.mjs";
4
- import { a as blockStop, c as hasUncheckedSelfReview, n as isGateActive, o as countUncheckedNextSteps, u as tryReadActiveSpec } from "./review-gate-Cqj-CmHP.mjs";
2
+ import "./dispatcher-M-Rc8GYD.mjs";
3
+ import { o as readWorkedSlugs } from "./state-Dqi1EiD0.mjs";
4
+ import { a as blockStop, c as hasUncheckedSelfReview, n as isGateActive, o as countUncheckedNextSteps, u as tryReadActiveSpec } from "./review-gate-IIPdo-3r.mjs";
5
5
  //#region src/hooks/stop.ts
6
6
  /**
7
7
  * Stop handler:
8
8
  * - review-gate active → BLOCK (hard enforcement)
9
9
  * - unchecked Next Steps / self-review / incomplete spec → CONTEXT reminder (no block)
10
+ * - Session-scoped: only reminds about specs worked on in this session (via worked-slugs).
11
+ * Fallback: if no worked-slugs recorded (read-only session), uses current primary.
10
12
  * DEC-4: stop_hook_active=true → always allow (infinite loop prevention).
11
13
  */
12
14
  async function stop(ev) {
@@ -18,12 +20,15 @@ async function stop(ev) {
18
20
  }
19
21
  const spec = tryReadActiveSpec(ev.cwd);
20
22
  if (!spec || spec.status === "completed") return;
23
+ const workedSlugs = ev.cwd ? readWorkedSlugs(ev.cwd) : [];
24
+ if (workedSlugs.length > 0 && !workedSlugs.includes(spec.slug)) return;
21
25
  const reminders = [];
22
26
  const unchecked = countUncheckedNextSteps(ev.cwd, spec.slug);
23
27
  if (unchecked > 0) reminders.push(`${unchecked} unchecked Next Steps in spec '${spec.slug}'`);
24
28
  if (hasUncheckedSelfReview(ev.cwd, spec.slug)) reminders.push("Self-review not completed");
25
29
  reminders.push("When done, call `dossier action=complete` to close the spec");
26
- emitAdditionalContext("Stop", `[CONTEXT] Spec '${spec.slug}' reminders: ${reminders.join("; ")}`);
30
+ const msg = `[CONTEXT] Spec '${spec.slug}' reminders: ${reminders.join("; ")}`;
31
+ process.stdout.write(`${JSON.stringify({ systemMessage: msg })}\n`);
27
32
  }
28
33
  //#endregion
29
34
  export { stop };
@@ -2,7 +2,6 @@
2
2
  import { createRequire } from "node:module";
3
3
  import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
4
4
  import { basename, join } from "node:path";
5
- import { createHash } from "node:crypto";
6
5
  //#region \0rolldown/runtime.js
7
6
  var __create = Object.create;
8
7
  var __defProp = Object.defineProperty;
@@ -6872,173 +6871,4 @@ function removeTask(projectPath, taskSlug) {
6872
6871
  return false;
6873
6872
  }
6874
6873
  //#endregion
6875
- //#region src/store/knowledge.ts
6876
- function contentHash(content) {
6877
- return createHash("sha256").update(content).digest("hex");
6878
- }
6879
- function upsertKnowledge(store, row) {
6880
- const now = (/* @__PURE__ */ new Date()).toISOString();
6881
- if (!row.createdAt) row.createdAt = now;
6882
- row.updatedAt = now;
6883
- row.contentHash = contentHash(row.content);
6884
- const existing = store.db.prepare("SELECT id, content_hash FROM knowledge_index WHERE project_remote = ? AND project_path = ? AND file_path = ?").get(row.projectRemote, row.projectPath, row.filePath);
6885
- if (existing && existing.content_hash === row.contentHash) {
6886
- row.id = existing.id;
6887
- return {
6888
- id: existing.id,
6889
- changed: false
6890
- };
6891
- }
6892
- const result = store.db.prepare(`
6893
- INSERT INTO knowledge_index
6894
- (file_path, content_hash, title, content, sub_type,
6895
- project_remote, project_path, project_name, branch,
6896
- created_at, updated_at, hit_count, last_accessed, enabled)
6897
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, '', 1)
6898
- ON CONFLICT(project_remote, project_path, file_path) DO UPDATE SET
6899
- content_hash = excluded.content_hash,
6900
- title = excluded.title,
6901
- content = excluded.content,
6902
- sub_type = excluded.sub_type,
6903
- project_name = excluded.project_name,
6904
- branch = excluded.branch,
6905
- updated_at = excluded.updated_at
6906
- `).run(row.filePath, row.contentHash, row.title, row.content, row.subType, row.projectRemote, row.projectPath, row.projectName, row.branch, row.createdAt, row.updatedAt);
6907
- const id = Number(result.lastInsertRowid);
6908
- row.id = id;
6909
- return {
6910
- id,
6911
- changed: true
6912
- };
6913
- }
6914
- function getKnowledgeByID(store, id) {
6915
- const row = store.db.prepare(`
6916
- SELECT id, file_path, content_hash, title, content, sub_type,
6917
- project_remote, project_path, project_name, branch,
6918
- created_at, updated_at, hit_count, last_accessed, enabled
6919
- FROM knowledge_index WHERE id = ?
6920
- `).get(id);
6921
- return row ? mapRow(row) : void 0;
6922
- }
6923
- function getKnowledgeByIDs(store, ids) {
6924
- if (ids.length === 0) return [];
6925
- const placeholders = ids.map(() => "?").join(",");
6926
- return store.db.prepare(`
6927
- SELECT id, file_path, content_hash, title, content, sub_type,
6928
- project_remote, project_path, project_name, branch,
6929
- created_at, updated_at, hit_count, last_accessed, enabled
6930
- FROM knowledge_index WHERE id IN (${placeholders})
6931
- `).all(...ids).map(mapRow);
6932
- }
6933
- function setKnowledgeEnabled(store, id, enabled) {
6934
- store.db.prepare("UPDATE knowledge_index SET enabled = ? WHERE id = ?").run(enabled ? 1 : 0, id);
6935
- }
6936
- function incrementHitCount(store, ids) {
6937
- if (ids.length === 0) return;
6938
- const now = (/* @__PURE__ */ new Date()).toISOString();
6939
- const placeholders = ids.map(() => "?").join(",");
6940
- store.db.prepare(`UPDATE knowledge_index SET hit_count = hit_count + 1, last_accessed = ?
6941
- WHERE id IN (${placeholders})`).run(now, ...ids);
6942
- }
6943
- function promoteSubType(store, id, newSubType) {
6944
- const now = (/* @__PURE__ */ new Date()).toISOString();
6945
- if (store.db.prepare("UPDATE knowledge_index SET sub_type = ?, updated_at = ? WHERE id = ? AND enabled = 1").run(newSubType, now, id).changes === 0) throw new Error(`store: promote sub_type: knowledge ${id} not found or disabled`);
6946
- }
6947
- function getPromotionCandidates(store) {
6948
- return store.db.prepare(`
6949
- SELECT id, file_path, content_hash, title, content, sub_type,
6950
- project_remote, project_path, project_name, branch,
6951
- created_at, updated_at, hit_count, last_accessed, enabled
6952
- FROM knowledge_index
6953
- WHERE enabled = 1
6954
- AND (sub_type = 'pattern' AND hit_count >= 15)
6955
- ORDER BY hit_count DESC
6956
- `).all().map(mapRow);
6957
- }
6958
- function getKnowledgeStats(store) {
6959
- const agg = store.db.prepare("SELECT COUNT(*) as total, COALESCE(AVG(hit_count), 0) as avg_hits FROM knowledge_index WHERE enabled = 1").get();
6960
- const bySubType = {};
6961
- const subtypeRows = store.db.prepare("SELECT sub_type, COUNT(*) as cnt FROM knowledge_index WHERE enabled = 1 GROUP BY sub_type").all();
6962
- for (const r of subtypeRows) bySubType[r.sub_type] = r.cnt;
6963
- const topRows = store.db.prepare(`
6964
- SELECT id, file_path, content_hash, title, content, sub_type,
6965
- project_remote, project_path, project_name, branch,
6966
- created_at, updated_at, hit_count, last_accessed, enabled
6967
- FROM knowledge_index WHERE enabled = 1
6968
- ORDER BY hit_count DESC LIMIT 5
6969
- `).all();
6970
- return {
6971
- total: agg?.total ?? 0,
6972
- avgHitCount: agg?.avg_hits ?? 0,
6973
- bySubType,
6974
- topAccessed: topRows.map(mapRow)
6975
- };
6976
- }
6977
- function searchKnowledgeKeyword(store, query, limit) {
6978
- const escaped = escapeLIKEContains(query);
6979
- return store.db.prepare(`
6980
- SELECT id, file_path, content_hash, title, content, sub_type,
6981
- project_remote, project_path, project_name, branch,
6982
- created_at, updated_at, hit_count, last_accessed, enabled
6983
- FROM knowledge_index
6984
- WHERE enabled = 1 AND (content LIKE ? ESCAPE '\\' OR title LIKE ? ESCAPE '\\')
6985
- ORDER BY hit_count DESC LIMIT ?
6986
- `).all(escaped, escaped, limit).map(mapRow);
6987
- }
6988
- function getRecentDecisions(store, projectRemote, projectPath, sinceISO, limit) {
6989
- return store.db.prepare(`
6990
- SELECT title, content, created_at FROM knowledge_index
6991
- WHERE sub_type = 'decision'
6992
- AND project_remote = ? AND project_path = ?
6993
- AND created_at > ? AND enabled = 1
6994
- ORDER BY created_at DESC LIMIT ?
6995
- `).all(projectRemote, projectPath, sinceISO, limit).map((r) => ({
6996
- title: r.title,
6997
- content: r.content,
6998
- createdAt: r.created_at
6999
- }));
7000
- }
7001
- function deleteOrphanKnowledge(store, projectRemote, projectPath, branch, validFilePaths) {
7002
- const rows = store.db.prepare("SELECT id, file_path FROM knowledge_index WHERE project_remote = ? AND project_path = ? AND branch = ?").all(projectRemote, projectPath, branch);
7003
- let deleted = 0;
7004
- const delEmbed = store.db.prepare("DELETE FROM embeddings WHERE source = 'knowledge' AND source_id = ?");
7005
- const delKnowledge = store.db.prepare("DELETE FROM knowledge_index WHERE id = ?");
7006
- store.db.transaction(() => {
7007
- for (const row of rows) if (!validFilePaths.has(row.file_path)) {
7008
- delEmbed.run(row.id);
7009
- delKnowledge.run(row.id);
7010
- deleted++;
7011
- }
7012
- })();
7013
- return deleted;
7014
- }
7015
- function countKnowledge(store, projectRemote, projectPath) {
7016
- return store.db.prepare("SELECT COUNT(*) as cnt FROM knowledge_index WHERE project_remote = ? AND project_path = ? AND enabled = 1").get(projectRemote, projectPath).cnt;
7017
- }
7018
- function escapeLIKEContains(s) {
7019
- s = s.replaceAll("\\", "\\\\");
7020
- s = s.replaceAll("%", "\\%");
7021
- s = s.replaceAll("_", "\\_");
7022
- return `%${s}%`;
7023
- }
7024
- function mapRow(r) {
7025
- return {
7026
- id: r.id,
7027
- filePath: r.file_path,
7028
- contentHash: r.content_hash,
7029
- title: r.title,
7030
- content: r.content,
7031
- subType: r.sub_type,
7032
- projectRemote: r.project_remote,
7033
- projectPath: r.project_path,
7034
- projectName: r.project_name,
7035
- branch: r.branch,
7036
- createdAt: r.created_at,
7037
- updatedAt: r.updated_at,
7038
- hitCount: r.hit_count,
7039
- lastAccessed: r.last_accessed,
7040
- enabled: r.enabled === 1
7041
- };
7042
- }
7043
- //#endregion
7044
- export { __toESM as A, rootDir as C, require_dist as D, writeActiveState as E, __commonJSMin as O, reviewStatusFor as S, verifyReviewFile as T, detectSize as _, getKnowledgeStats as a, readActiveState as b, incrementHitCount as c, searchKnowledgeKeyword as d, setKnowledgeEnabled as f, completeTask as g, VALID_SLUG as h, getKnowledgeByIDs as i, __exportAll as k, mapRow as l, SpecDir as m, deleteOrphanKnowledge as n, getPromotionCandidates as o, upsertKnowledge as p, getKnowledgeByID as r, getRecentDecisions as s, countKnowledge as t, promoteSubType as u, filesForSize as v, switchActive as w, removeTask as x, readActive as y };
6874
+ export { __toESM as _, filesForSize as a, removeTask as c, switchActive as d, verifyReviewFile as f, __exportAll as g, __commonJSMin as h, detectSize as i, reviewStatusFor as l, require_dist as m, VALID_SLUG as n, readActive as o, writeActiveState as p, completeTask as r, readActiveState as s, SpecDir as t, rootDir as u };
@@ -1,12 +1,12 @@
1
1
  #!/usr/bin/env node
2
- import { b as readActiveState } from "./knowledge-kk6LxnnT.mjs";
3
- import { r as subTypeBoost } from "./fts-Ck8wjOIE.mjs";
4
- import { Embedder } from "./embedder-CuUqZpze.mjs";
5
- import "./dispatcher-DZUzmm1Y.mjs";
6
- import { n as trackHitCounts, r as truncate, t as searchPipeline } from "./helpers-rMTa9O_h.mjs";
7
- import { openDefaultCached } from "./store-DenWQkPw.mjs";
8
- import { t as emitDirectives } from "./directives-Bm6w4JNk.mjs";
9
- import { a as writeLastIntent, o as writeStateJSON, r as readStateJSON, t as IMPLEMENT_INTENTS } from "./state-C0v-bfFb.mjs";
2
+ import { s as readActiveState } from "./types-DFsKNXVY.mjs";
3
+ import { r as subTypeBoost } from "./fts-Buk8fkl1.mjs";
4
+ import { Embedder } from "./embedder-CFkDPOku.mjs";
5
+ import "./dispatcher-M-Rc8GYD.mjs";
6
+ import { n as trackHitCounts, r as truncate, t as searchPipeline } from "./helpers-CvI9bVCq.mjs";
7
+ import { openDefaultCached } from "./store-D4fokoGA.mjs";
8
+ import { t as emitDirectives } from "./directives-BnbWxhap.mjs";
9
+ import { c as writeLastIntent, i as readStateJSON, l as writeStateJSON, o as readWorkedSlugs, t as IMPLEMENT_INTENTS } from "./state-Dqi1EiD0.mjs";
10
10
  import { existsSync } from "node:fs";
11
11
  import { join } from "node:path";
12
12
  //#region src/hooks/user-prompt.ts
@@ -203,6 +203,7 @@ function classifyIntent(prompt) {
203
203
  /**
204
204
  * FR-5/FR-7: Check if spec is required or unapproved before implementation.
205
205
  * Stage 1: No spec exists → DIRECTIVE to create one.
206
+ * Stage 1.5: Spec exists + implement intent → WARNING to confirm or create new spec.
206
207
  * Stage 2: Spec exists but not approved (M/L/XL) → DIRECTIVE to get review.
207
208
  */
208
209
  function checkSpecRequired(cwd, intent) {
@@ -244,6 +245,12 @@ function checkSpecRequired(cwd, intent) {
244
245
  ],
245
246
  spiritVsLetter: true
246
247
  };
248
+ if (taskSlug) {
249
+ if (!readWorkedSlugs(cwd).includes(taskSlug)) return {
250
+ level: "WARNING",
251
+ message: `Active spec '${taskSlug}' exists. If this is a different task, create a new spec first via /alfred:brief or dossier action=init. Use AskUserQuestion to confirm with the user whether this work is part of '${taskSlug}' or a new task.`
252
+ };
253
+ }
247
254
  } catch {}
248
255
  return null;
249
256
  }
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { k as __exportAll } from "./knowledge-kk6LxnnT.mjs";
2
+ import { g as __exportAll } from "./types-DFsKNXVY.mjs";
3
3
  //#region src/store/vectors.ts
4
4
  var vectors_exports = /* @__PURE__ */ __exportAll({
5
5
  cosineSimilarity: () => cosineSimilarity,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-alfred",
3
- "version": "0.3.3",
3
+ "version": "0.3.5",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "alfred": "dist/cli.mjs"
File without changes