spets 0.2.0 → 0.2.2

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.
@@ -12,6 +12,8 @@ import { join } from "path";
12
12
  import matter from "gray-matter";
13
13
  var KNOWLEDGE_DIR = "knowledge";
14
14
  var GUIDE_FILE = "guide.md";
15
+ var MAX_SUMMARY_CHARS = 2e3;
16
+ var MAX_KNOWLEDGE_CHARS = 8e3;
15
17
  function getKnowledgeDir(cwd = process.cwd()) {
16
18
  return join(getSpetsDir(cwd), KNOWLEDGE_DIR);
17
19
  }
@@ -58,10 +60,21 @@ function saveKnowledge(filename, content, source, cwd = process.cwd()) {
58
60
  ensureKnowledgeDir(cwd);
59
61
  const knowledgeDir = getKnowledgeDir(cwd);
60
62
  const filePath = join(knowledgeDir, `${filename}.md`);
63
+ let createdAt = (/* @__PURE__ */ new Date()).toISOString();
64
+ if (existsSync(filePath)) {
65
+ const existing = readFileSync(filePath, "utf-8");
66
+ const { data } = matter(existing);
67
+ if (data.createdAt) {
68
+ createdAt = data.createdAt;
69
+ }
70
+ }
61
71
  const frontmatter = {
62
72
  source,
63
- createdAt: (/* @__PURE__ */ new Date()).toISOString()
73
+ createdAt
64
74
  };
75
+ if (existsSync(filePath)) {
76
+ frontmatter.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
77
+ }
65
78
  const fileContent = matter.stringify(content, frontmatter);
66
79
  writeFileSync(filePath, fileContent);
67
80
  }
@@ -75,21 +88,69 @@ function loadGuide(cwd = process.cwd()) {
75
88
  const { content } = matter(fileContent);
76
89
  return content.trim();
77
90
  }
78
- function buildAvailableKnowledgeSection(cwd = process.cwd()) {
91
+ function knowledgePriority(filename) {
92
+ if (filename.startsWith("rule--")) return 0;
93
+ if (filename.startsWith("pattern--")) return 1;
94
+ if (filename.startsWith("pref--")) return 2;
95
+ return 3;
96
+ }
97
+ function buildKnowledgeSummarySection(cwd = process.cwd()) {
79
98
  const files = listKnowledgeFiles(cwd);
80
99
  if (files.length === 0) {
81
100
  return "";
82
101
  }
102
+ const sorted = [...files].sort((a, b) => knowledgePriority(a) - knowledgePriority(b));
103
+ const lines = [];
104
+ let charCount = 0;
105
+ let truncated = 0;
106
+ for (const filename of sorted) {
107
+ const entry = loadKnowledgeFile(filename, cwd);
108
+ const firstLine = entry?.content.split("\n")[0]?.trim() || "";
109
+ const line = `- ${filename}: ${firstLine}`;
110
+ if (charCount + line.length > MAX_SUMMARY_CHARS) {
111
+ truncated = sorted.length - lines.length;
112
+ break;
113
+ }
114
+ lines.push(line);
115
+ charCount += line.length;
116
+ }
83
117
  const parts = [];
84
- parts.push("## Available Knowledge");
118
+ parts.push("## Knowledge Summary");
85
119
  parts.push("");
86
- parts.push("\uB2E4\uC74C \uC9C0\uC2DD \uD30C\uC77C\uB4E4\uC774 \uC774 \uD504\uB85C\uC81D\uD2B8\uC5D0 \uC800\uC7A5\uB418\uC5B4 \uC788\uC2B5\uB2C8\uB2E4:");
87
- for (const file of files) {
88
- parts.push(`- ${file}`);
120
+ parts.push("\uC774 \uD504\uB85C\uC81D\uD2B8\uC5D0 \uC800\uC7A5\uB41C \uC9C0\uC2DD:");
121
+ parts.push("");
122
+ parts.push(...lines);
123
+ if (truncated > 0) {
124
+ parts.push(`- ... and ${truncated} more`);
89
125
  }
90
126
  parts.push("");
91
- parts.push("\uD604\uC7AC \uD0DC\uC2A4\uD06C\uC5D0 \uCC38\uACE0\uD560 \uC9C0\uC2DD\uC774 \uC788\uB2E4\uBA74 JSON \uCD9C\uB825\uC758 `knowledgeRequests` \uD544\uB4DC\uC5D0 \uD30C\uC77C\uBA85\uC744 \uCD94\uAC00\uD558\uC138\uC694.");
127
+ return parts.join("\n");
128
+ }
129
+ function buildBudgetedKnowledgeSection(entries, heading = "## Loaded Knowledge") {
130
+ if (entries.length === 0) {
131
+ return "";
132
+ }
133
+ const parts = [];
134
+ parts.push(heading);
92
135
  parts.push("");
136
+ let charCount = 0;
137
+ for (const entry of entries) {
138
+ const section = `### ${entry.filename}
139
+
140
+ ${entry.content}
141
+ `;
142
+ if (charCount + section.length > MAX_KNOWLEDGE_CHARS) {
143
+ const remaining = entries.length - parts.filter((p) => p.startsWith("### ")).length;
144
+ if (remaining > 0) {
145
+ parts.push(`
146
+ *... ${remaining} more entries truncated due to budget*
147
+ `);
148
+ }
149
+ break;
150
+ }
151
+ parts.push(section);
152
+ charCount += section.length;
153
+ }
93
154
  return parts.join("\n");
94
155
  }
95
156
 
@@ -210,7 +271,8 @@ export {
210
271
  loadKnowledgeFiles,
211
272
  saveKnowledge,
212
273
  loadGuide,
213
- buildAvailableKnowledgeSection,
274
+ buildKnowledgeSummarySection,
275
+ buildBudgetedKnowledgeSection,
214
276
  renderKnowledgeJSON,
215
277
  runKnowledgeInteractive
216
278
  };
package/dist/index.js CHANGED
@@ -22,7 +22,8 @@ import {
22
22
  runDocsInteractive
23
23
  } from "./chunk-3JIHGW47.js";
24
24
  import {
25
- buildAvailableKnowledgeSection,
25
+ buildBudgetedKnowledgeSection,
26
+ buildKnowledgeSummarySection,
26
27
  getKnowledgeDir,
27
28
  listKnowledgeFiles,
28
29
  loadGuide,
@@ -31,7 +32,7 @@ import {
31
32
  renderKnowledgeJSON,
32
33
  runKnowledgeInteractive,
33
34
  saveKnowledge
34
- } from "./chunk-3QH66XVU.js";
35
+ } from "./chunk-OIFVTELS.js";
35
36
  import {
36
37
  clearConfigCache,
37
38
  getConfigPath,
@@ -272,62 +273,37 @@ async function listPlugins() {
272
273
  function getClaudeSkillContent() {
273
274
  return `# Spets Executor
274
275
 
275
- You execute spets orchestrator commands. Parse JSON, follow instructions, repeat.
276
+ You execute spets workflow commands. Follow orchestrator instructions exactly.
276
277
 
277
278
  ## Startup
278
279
 
279
280
  IF \`$ARGUMENTS\` starts with "resume":
280
- 1. RUN \`npx spets orchestrate resume\`
281
+ RUN \`npx spets orchestrate resume\`
282
+ ELSE IF \`$ARGUMENTS\` starts with "list":
283
+ RUN \`npx spets orchestrate list\`
281
284
  ELSE:
282
- 1. RUN \`npx spets orchestrate init "$ARGUMENTS"\`
283
-
284
- Then GOTO **Loop**.
285
+ RUN \`npx spets orchestrate init "$ARGUMENTS"\`
285
286
 
286
287
  ## Loop
287
288
 
288
- 1. PARSE JSON response
289
- 2. SWITCH on \`type\`:
290
-
291
- ### type="phase"
292
- - EXECUTE what \`prompt\` says
293
- - RUN \`onComplete\` with your output as JSON argument
294
- - GOTO Loop
295
-
296
- ### type="checkpoint", checkpoint="clarify"
297
- - ASK user each decision in \`decisions[]\` using AskUserQuestion
298
- - Format each decision as a question with its options
299
- - RUN \`onComplete\` with \`[{decisionId, selectedOptionId}, ...]\`
300
- - GOTO Loop
301
-
302
- ### type="checkpoint", checkpoint="approve"
303
- - READ \`specPath\`, summarize key points to user
304
- - ASK user using AskUserQuestion with options: Approve / Revise / Reject / Stop
305
- - RUN matching \`onComplete\` based on user's choice
306
- - GOTO Loop
307
-
308
- ### type="checkpoint", checkpoint="knowledge"
309
- - Show \`suggestedKnowledge[]\` to user
310
- - ASK user if they want to save, modify, or skip
311
- - If save: RUN \`onComplete\` with entries JSON
312
- - If skip: RUN \`onSkip\`
313
- - GOTO Loop
289
+ 1. Parse the JSON response
290
+ 2. Read the \`instructions\` field
291
+ 3. Follow the instructions exactly
292
+ 4. Repeat until the response says to stop
314
293
 
315
- ### type="list"
316
- - Show \`tasks[]\` to user with taskId, description, status, currentStep
317
- - ASK user which task to resume
318
- - RUN \`npx spets orchestrate resume <selectedTaskId>\`
319
- - GOTO Loop
294
+ ## Rules
320
295
 
321
- ### type="complete" or "error"
322
- - PRINT message
323
- - STOP
296
+ - NEVER skip or modify orchestrator commands
297
+ - NEVER improvise steps \u2014 only do what instructions say
298
+ - Always pass JSON output as a single-quoted string argument
299
+ - Minify JSON when passing as command arguments
324
300
 
325
301
  ## Forbidden
326
302
 
327
303
  EnterPlanMode, TaskCreate, TaskUpdate
328
304
 
329
305
  $ARGUMENTS
330
- description: Task description for the workflow (or "resume" to continue a previous workflow)
306
+ description: Task description for the workflow (or "resume" to list/continue previous workflows)
331
307
  `;
332
308
  }
333
309
  function getCodexSkillContent() {
@@ -338,65 +314,37 @@ description: SDD workflow executor - orchestrator-controlled spec-driven develop
338
314
 
339
315
  # Spets Executor
340
316
 
341
- You execute spets orchestrator commands. Parse JSON, follow instructions, repeat.
317
+ You execute spets workflow commands. Follow orchestrator instructions exactly.
342
318
 
343
319
  ## Startup
344
320
 
345
321
  IF \`$ARGUMENTS\` starts with "resume":
346
- 1. RUN \`npx spets orchestrate resume\`
322
+ RUN \`npx spets orchestrate resume\`
323
+ ELSE IF \`$ARGUMENTS\` starts with "list":
324
+ RUN \`npx spets orchestrate list\`
347
325
  ELSE:
348
- 1. RUN \`npx spets orchestrate init "$ARGUMENTS"\`
349
-
350
- Then GOTO **Loop**.
326
+ RUN \`npx spets orchestrate init "$ARGUMENTS"\`
351
327
 
352
328
  ## Loop
353
329
 
354
- 1. PARSE JSON response
355
- 2. SWITCH on \`type\`:
356
-
357
- ### type="phase"
358
- - EXECUTE what \`prompt\` says
359
- - RUN \`onComplete\` with your output as minified JSON argument
360
- - GOTO Loop
361
-
362
- ### type="checkpoint", checkpoint="clarify"
363
- - FORMAT decisions as table for user:
364
- | # | Decision | Context | Options |
365
- |---|----------|---------|---------|
366
- | d1 | ... | ... | 1. ... 2. ... |
367
- - ASK user to pick an option for each decision
368
- - RUN \`onComplete\` with \`[{decisionId, selectedOptionId}, ...]\`
369
- - GOTO Loop
370
-
371
- ### type="checkpoint", checkpoint="approve"
372
- - READ \`specPath\`, summarize key points to user
373
- - ASK user with options: Approve / Revise / Reject / Stop
374
- - RUN matching \`onComplete\` based on user's choice
375
- - GOTO Loop
376
-
377
- ### type="checkpoint", checkpoint="knowledge"
378
- - Show \`suggestedKnowledge[]\` to user
379
- - ASK user if they want to save, modify, or skip
380
- - If save: RUN \`onComplete\` with entries JSON
381
- - If skip: RUN \`onSkip\`
382
- - GOTO Loop
330
+ 1. Parse the JSON response
331
+ 2. Read the \`instructions\` field
332
+ 3. Follow the instructions exactly
333
+ 4. Repeat until the response says to stop
383
334
 
384
- ### type="list"
385
- - Show \`tasks[]\` to user with taskId, description, status, currentStep
386
- - ASK user which task to resume
387
- - RUN \`npx spets orchestrate resume <selectedTaskId>\`
388
- - GOTO Loop
335
+ ## Rules
389
336
 
390
- ### type="complete" or "error"
391
- - PRINT message
392
- - STOP
337
+ - NEVER skip or modify orchestrator commands
338
+ - NEVER improvise steps \u2014 only do what instructions say
339
+ - Always pass JSON output as a single-quoted string argument
340
+ - Minify JSON when passing as command arguments
393
341
 
394
342
  ## Forbidden
395
343
 
396
344
  Planning mode, task tracking tools
397
345
 
398
346
  $ARGUMENTS
399
- description: Task description for the workflow (or "resume" to continue a previous workflow)
347
+ description: Task description for the workflow (or "resume" to list/continue previous workflows)
400
348
  `;
401
349
  }
402
350
  function getGeminiSkillContent() {
@@ -407,61 +355,37 @@ description: SDD workflow executor - orchestrator-controlled spec-driven develop
407
355
 
408
356
  # Spets Executor
409
357
 
410
- You execute spets orchestrator commands. Parse JSON, follow instructions, repeat.
358
+ You execute spets workflow commands. Follow orchestrator instructions exactly.
411
359
 
412
360
  ## Startup
413
361
 
414
362
  IF \`$ARGUMENTS\` starts with "resume":
415
- 1. RUN \`npx spets orchestrate resume\`
363
+ RUN \`npx spets orchestrate resume\`
364
+ ELSE IF \`$ARGUMENTS\` starts with "list":
365
+ RUN \`npx spets orchestrate list\`
416
366
  ELSE:
417
- 1. RUN \`npx spets orchestrate init "$ARGUMENTS"\`
418
-
419
- Then GOTO **Loop**.
367
+ RUN \`npx spets orchestrate init "$ARGUMENTS"\`
420
368
 
421
369
  ## Loop
422
370
 
423
- 1. PARSE JSON response
424
- 2. SWITCH on \`type\`:
425
-
426
- ### type="phase"
427
- - EXECUTE what \`prompt\` says
428
- - RUN \`onComplete\` with your output as JSON argument
429
- - GOTO Loop
430
-
431
- ### type="checkpoint", checkpoint="clarify"
432
- - ASK user each decision in \`decisions[]\`
433
- - RUN \`onComplete\` with \`[{decisionId, selectedOptionId}, ...]\`
434
- - GOTO Loop
435
-
436
- ### type="checkpoint", checkpoint="approve"
437
- - READ \`specPath\`, summarize key points to user
438
- - ASK user with options: Approve / Revise / Reject / Stop
439
- - RUN matching \`onComplete\` based on user's choice
440
- - GOTO Loop
441
-
442
- ### type="checkpoint", checkpoint="knowledge"
443
- - Show \`suggestedKnowledge[]\` to user
444
- - ASK user if they want to save, modify, or skip
445
- - If save: RUN \`onComplete\` with entries JSON
446
- - If skip: RUN \`onSkip\`
447
- - GOTO Loop
371
+ 1. Parse the JSON response
372
+ 2. Read the \`instructions\` field
373
+ 3. Follow the instructions exactly
374
+ 4. Repeat until the response says to stop
448
375
 
449
- ### type="list"
450
- - Show \`tasks[]\` to user with taskId, description, status, currentStep
451
- - ASK user which task to resume
452
- - RUN \`npx spets orchestrate resume <selectedTaskId>\`
453
- - GOTO Loop
376
+ ## Rules
454
377
 
455
- ### type="complete" or "error"
456
- - PRINT message
457
- - STOP
378
+ - NEVER skip or modify orchestrator commands
379
+ - NEVER improvise steps \u2014 only do what instructions say
380
+ - Always pass JSON output as a single-quoted string argument
381
+ - Minify JSON when passing as command arguments
458
382
 
459
383
  ## Forbidden
460
384
 
461
385
  Planning mode, task tracking tools
462
386
 
463
387
  $ARGUMENTS
464
- description: Task description for the workflow (or "resume" to continue a previous workflow)
388
+ description: Task description for the workflow (or "resume" to list/continue previous workflows)
465
389
  `;
466
390
  }
467
391
 
@@ -503,19 +427,26 @@ async function initCommand(options) {
503
427
  const hookTemplate = join2(__dirname, "..", "templates", "hooks", "cleanup-branch.sh");
504
428
  if (existsSync2(hookTemplate)) {
505
429
  const hookDest = join2(spetsDir, "hooks", "cleanup-branch.sh");
506
- cpSync(hookTemplate, hookDest);
507
- try {
508
- execSync(`chmod +x "${hookDest}"`);
509
- } catch {
430
+ if (!existsSync2(hookDest)) {
431
+ cpSync(hookTemplate, hookDest);
432
+ try {
433
+ execSync(`chmod +x "${hookDest}"`);
434
+ } catch {
435
+ }
510
436
  }
511
437
  }
512
438
  const knowledgeGuideTemplate = join2(__dirname, "..", "templates", "knowledge", "guide.md");
513
439
  if (existsSync2(knowledgeGuideTemplate)) {
514
440
  const guideDest = join2(spetsDir, "knowledge", "guide.md");
515
- cpSync(knowledgeGuideTemplate, guideDest);
441
+ if (!existsSync2(guideDest)) {
442
+ cpSync(knowledgeGuideTemplate, guideDest);
443
+ }
516
444
  }
517
445
  const githubInfo = getGitHubInfoFromRemote();
518
- writeFileSync2(join2(spetsDir, "config.yml"), getDefaultConfig(githubInfo));
446
+ const configPath = join2(spetsDir, "config.yml");
447
+ if (!existsSync2(configPath)) {
448
+ writeFileSync2(configPath, getDefaultConfig(githubInfo));
449
+ }
519
450
  createDefaultSteps(spetsDir);
520
451
  createClaudeCommand(cwd);
521
452
  printEnhancedInitOutput(options);
@@ -739,20 +670,23 @@ async function runInteractiveSetup(cwd, spetsDir, options) {
739
670
  const hookTemplate = join2(__dirname, "..", "templates", "hooks", "cleanup-branch.sh");
740
671
  if (existsSync2(hookTemplate)) {
741
672
  const hookDest = join2(spetsDir, "hooks", "cleanup-branch.sh");
742
- cpSync(hookTemplate, hookDest);
743
- try {
744
- execSync(`chmod +x "${hookDest}"`);
745
- } catch {
673
+ if (!existsSync2(hookDest)) {
674
+ cpSync(hookTemplate, hookDest);
675
+ try {
676
+ execSync(`chmod +x "${hookDest}"`);
677
+ } catch {
678
+ }
746
679
  }
747
680
  }
748
681
  const knowledgeGuideTemplate = join2(__dirname, "..", "templates", "knowledge", "guide.md");
749
- if (existsSync2(knowledgeGuideTemplate)) {
750
- cpSync(knowledgeGuideTemplate, join2(spetsDir, "knowledge", "guide.md"));
682
+ const guideDest = join2(spetsDir, "knowledge", "guide.md");
683
+ if (existsSync2(knowledgeGuideTemplate) && !existsSync2(guideDest)) {
684
+ cpSync(knowledgeGuideTemplate, guideDest);
685
+ }
686
+ const configPath = join2(spetsDir, "config.yml");
687
+ if (!existsSync2(configPath)) {
688
+ writeFileSync2(configPath, getDefaultConfig(githubInfo, { agent, githubEnabled }));
751
689
  }
752
- writeFileSync2(
753
- join2(spetsDir, "config.yml"),
754
- getDefaultConfig(githubInfo, { agent, githubEnabled })
755
- );
756
690
  createDefaultSteps(spetsDir);
757
691
  createClaudeCommand(cwd);
758
692
  if (githubEnabled && githubInfo) {
@@ -764,10 +698,16 @@ async function runInteractiveSetup(cwd, spetsDir, options) {
764
698
  function createDefaultSteps(spetsDir) {
765
699
  const planDir = join2(spetsDir, "steps", "01-plan");
766
700
  mkdirSync2(planDir, { recursive: true });
767
- writeFileSync2(join2(planDir, "template.md"), getPlanTemplate());
701
+ const planTemplatePath = join2(planDir, "template.md");
702
+ if (!existsSync2(planTemplatePath)) {
703
+ writeFileSync2(planTemplatePath, getPlanTemplate());
704
+ }
768
705
  const implementDir = join2(spetsDir, "steps", "02-implement");
769
706
  mkdirSync2(implementDir, { recursive: true });
770
- writeFileSync2(join2(implementDir, "template.md"), getImplementTemplate());
707
+ const implementTemplatePath = join2(implementDir, "template.md");
708
+ if (!existsSync2(implementTemplatePath)) {
709
+ writeFileSync2(implementTemplatePath, getImplementTemplate());
710
+ }
771
711
  }
772
712
  function getPlanTemplate() {
773
713
  const fullTemplate = readFileSync(join2(__dirname, "..", "templates", "steps", "01-plan", "template.md"), "utf-8");
@@ -1045,7 +985,7 @@ function buildExplorePrompt(params) {
1045
985
  parts.push("");
1046
986
  const config = loadConfig(cwd);
1047
987
  if (config.knowledge?.inject !== false) {
1048
- const knowledgeSection = buildAvailableKnowledgeSection(cwd);
988
+ const knowledgeSection = buildKnowledgeSummarySection(cwd);
1049
989
  if (knowledgeSection) {
1050
990
  parts.push(knowledgeSection);
1051
991
  }
@@ -1125,6 +1065,7 @@ function buildExploreTeamSection() {
1125
1065
 
1126
1066
  // src/core/prompts/clarify.ts
1127
1067
  function buildClarifyPrompt(params) {
1068
+ const cwd = params.cwd || process.cwd();
1128
1069
  const parts = [];
1129
1070
  parts.push("# Clarify Phase");
1130
1071
  parts.push("");
@@ -1141,6 +1082,13 @@ function buildClarifyPrompt(params) {
1141
1082
  parts.push("");
1142
1083
  parts.push(params.gatheredContext);
1143
1084
  parts.push("");
1085
+ const config = loadConfig(cwd);
1086
+ if (config.knowledge?.inject !== false) {
1087
+ const knowledgeSection = buildKnowledgeSummarySection(cwd);
1088
+ if (knowledgeSection) {
1089
+ parts.push(knowledgeSection);
1090
+ }
1091
+ }
1144
1092
  if (params.loadedKnowledge && params.loadedKnowledge.length > 0) {
1145
1093
  parts.push("## Requested Knowledge");
1146
1094
  parts.push("");
@@ -1408,6 +1356,12 @@ function buildExecutePrompt(params) {
1408
1356
  }
1409
1357
  parts.push("");
1410
1358
  }
1359
+ if (config.knowledge?.inject !== false) {
1360
+ const knowledgeSection = buildKnowledgeSummarySection(cwd);
1361
+ if (knowledgeSection) {
1362
+ parts.push(knowledgeSection);
1363
+ }
1364
+ }
1411
1365
  if (params.loadedKnowledge && params.loadedKnowledge.length > 0) {
1412
1366
  parts.push("### Referenced Knowledge");
1413
1367
  parts.push("");
@@ -1518,6 +1472,20 @@ function buildVerifyPrompt(params) {
1518
1472
  parts.push(template);
1519
1473
  parts.push("");
1520
1474
  }
1475
+ const config = loadConfig(cwd);
1476
+ if (config.knowledge?.inject !== false) {
1477
+ const knowledgeSection = buildKnowledgeSummarySection(cwd);
1478
+ if (knowledgeSection) {
1479
+ parts.push(knowledgeSection);
1480
+ }
1481
+ }
1482
+ if (params.loadedKnowledge && params.loadedKnowledge.length > 0) {
1483
+ const knowledgeContent = buildBudgetedKnowledgeSection(params.loadedKnowledge);
1484
+ if (knowledgeContent) {
1485
+ parts.push(knowledgeContent);
1486
+ parts.push("");
1487
+ }
1488
+ }
1521
1489
  parts.push("## Verification Criteria");
1522
1490
  parts.push("");
1523
1491
  parts.push("1. **Accuracy** (0-100)");
@@ -1542,7 +1510,6 @@ function buildVerifyPrompt(params) {
1542
1510
  parts.push("");
1543
1511
  parts.push("If it fails, the output will be automatically revised (up to 3 attempts).");
1544
1512
  parts.push("");
1545
- const config = loadConfig(cwd);
1546
1513
  if (config.team?.enabled) {
1547
1514
  parts.push(buildVerifyTeamSection());
1548
1515
  }
@@ -1663,6 +1630,20 @@ function buildKnowledgeExtractPrompt(params) {
1663
1630
  parts.push("");
1664
1631
  }
1665
1632
  }
1633
+ const cwd = params.cwd || process.cwd();
1634
+ const existingFiles = listKnowledgeFiles(cwd);
1635
+ if (existingFiles.length > 0) {
1636
+ parts.push("## Existing Knowledge Entries");
1637
+ parts.push("");
1638
+ parts.push("\uB2E4\uC74C \uC9C0\uC2DD\uC774 \uC774\uBBF8 \uC800\uC7A5\uB418\uC5B4 \uC788\uC2B5\uB2C8\uB2E4. \uACB9\uCE58\uB294 \uB0B4\uC6A9\uC740 update, \uC0C8\uB85C\uC6B4 \uAC83\uB9CC create \uD558\uC138\uC694:");
1639
+ parts.push("");
1640
+ for (const filename of existingFiles) {
1641
+ const entry = loadKnowledgeFile(filename, cwd);
1642
+ const firstLine = entry?.content.split("\n")[0]?.trim() || "";
1643
+ parts.push(`- **${filename}**: ${firstLine}`);
1644
+ }
1645
+ parts.push("");
1646
+ }
1666
1647
  parts.push("## Your Task");
1667
1648
  parts.push("");
1668
1649
  parts.push("Extract knowledge that would be useful for future workflows:");
@@ -1678,6 +1659,10 @@ function buildKnowledgeExtractPrompt(params) {
1678
1659
  parts.push("- One-time decisions");
1679
1660
  parts.push("- Temporary workarounds");
1680
1661
  parts.push("");
1662
+ parts.push("**Update or Create:**");
1663
+ parts.push('- If an existing entry covers the same topic, use `"action": "update"` with the same filename');
1664
+ parts.push('- Only use `"action": "create"` for genuinely new knowledge');
1665
+ parts.push("");
1681
1666
  parts.push("## Output Format");
1682
1667
  parts.push("");
1683
1668
  parts.push("Output a JSON object with this structure:");
@@ -1688,7 +1673,8 @@ function buildKnowledgeExtractPrompt(params) {
1688
1673
  parts.push(" {");
1689
1674
  parts.push(' "filename": "pattern--error-handling",');
1690
1675
  parts.push(' "content": "The knowledge content in markdown format...",');
1691
- parts.push(' "reason": "Why this is worth saving"');
1676
+ parts.push(' "reason": "Why this is worth saving",');
1677
+ parts.push(' "action": "create"');
1692
1678
  parts.push(" }");
1693
1679
  parts.push(" ]");
1694
1680
  parts.push("}");
@@ -1706,6 +1692,36 @@ function buildKnowledgeExtractPrompt(params) {
1706
1692
  return parts.join("\n");
1707
1693
  }
1708
1694
 
1695
+ // src/orchestrator/instructions.ts
1696
+ function phaseWithJsonOutput(onComplete) {
1697
+ return `Execute the prompt. Then run:
1698
+ ${onComplete} '<your_json_output>'`;
1699
+ }
1700
+ function phaseExecute(outputFile, onComplete) {
1701
+ return `Execute the prompt \u2014 write the document to ${outputFile}. Then run:
1702
+ ${onComplete}`;
1703
+ }
1704
+ function checkpointClarify(onComplete) {
1705
+ return [
1706
+ `Present each decision to the user with its options. Collect their choices. Then run:`,
1707
+ ` ${onComplete} '<answers_json>'`,
1708
+ `Answer format: [{decisionId, selectedOptionId, customInput?}, ...]`
1709
+ ].join("\n");
1710
+ }
1711
+ function checkpointApprove(specPath, cmds) {
1712
+ return [
1713
+ `Read the document at ${specPath}. Summarize key points to the user. Ask: Approve / Revise / Reject / Stop. Then run the matching command:`,
1714
+ ` approve: ${cmds.approve}`,
1715
+ ` revise: ${cmds.revise}`,
1716
+ ` reject: ${cmds.reject}`,
1717
+ ` stop: ${cmds.stop}`
1718
+ ].join("\n");
1719
+ }
1720
+ var PRINT_MESSAGE_AND_STOP = "Print the message. Stop.";
1721
+ var PRINT_ERROR_AND_STOP = "Print the error. Stop.";
1722
+ var LIST_TASKS = "Show tasks to user. Ask which to resume. Run:\n npx spets orchestrate resume <selectedTaskId>";
1723
+ var LIST_EMPTY = "No resumable tasks found. Show this to the user. Stop.";
1724
+
1709
1725
  // src/orchestrator/responses.ts
1710
1726
  function buildPhaseExploreResponse(cwd, state) {
1711
1727
  const steps = getSteps(cwd);
@@ -1727,6 +1743,7 @@ function buildPhaseExploreResponse(cwd, state) {
1727
1743
  previousOutput,
1728
1744
  cwd
1729
1745
  });
1746
+ const onComplete = `npx spets orchestrate explore-done ${state.taskId}`;
1730
1747
  return {
1731
1748
  type: "phase",
1732
1749
  phase: "explore",
@@ -1739,7 +1756,8 @@ function buildPhaseExploreResponse(cwd, state) {
1739
1756
  context: {
1740
1757
  previousOutput
1741
1758
  },
1742
- onComplete: `npx spets orchestrate explore-done ${state.taskId}`
1759
+ onComplete,
1760
+ instructions: phaseWithJsonOutput(onComplete)
1743
1761
  };
1744
1762
  }
1745
1763
  function buildPhaseClarifyResponse(cwd, state) {
@@ -1754,6 +1772,7 @@ function buildPhaseClarifyResponse(cwd, state) {
1754
1772
  // Legacy support
1755
1773
  cwd
1756
1774
  });
1775
+ const onComplete = `npx spets orchestrate clarify-done ${state.taskId}`;
1757
1776
  return {
1758
1777
  type: "phase",
1759
1778
  phase: "clarify",
@@ -1765,7 +1784,8 @@ function buildPhaseClarifyResponse(cwd, state) {
1765
1784
  previousDecisions: state.decisionHistory,
1766
1785
  previousQA: state.qaHistory,
1767
1786
  // Legacy support
1768
- onComplete: `npx spets orchestrate clarify-done ${state.taskId}`
1787
+ onComplete,
1788
+ instructions: phaseWithJsonOutput(onComplete)
1769
1789
  };
1770
1790
  }
1771
1791
  function buildPhaseExecuteResponse(cwd, state) {
@@ -1808,6 +1828,8 @@ ${issues}`;
1808
1828
  verifyFeedback,
1809
1829
  cwd
1810
1830
  });
1831
+ const onComplete = `npx spets orchestrate execute-done ${state.taskId}`;
1832
+ const outputFile = join9(outputPath, state.taskId, `${state.currentStep}.md`);
1811
1833
  return {
1812
1834
  type: "phase",
1813
1835
  phase: "execute",
@@ -1822,11 +1844,12 @@ ${issues}`;
1822
1844
  context: {
1823
1845
  template: hasTemplate ? templatePath : void 0,
1824
1846
  previousOutput,
1825
- output: join9(outputPath, state.taskId, `${state.currentStep}.md`),
1847
+ output: outputFile,
1826
1848
  revisionFeedback: state.revisionFeedback,
1827
1849
  verifyFeedback
1828
1850
  },
1829
- onComplete: `npx spets orchestrate execute-done ${state.taskId}`
1851
+ onComplete,
1852
+ instructions: phaseExecute(outputFile, onComplete)
1830
1853
  };
1831
1854
  }
1832
1855
  function buildPhaseVerifyResponse(cwd, state) {
@@ -1842,8 +1865,10 @@ function buildPhaseVerifyResponse(cwd, state) {
1842
1865
  totalSteps: state.totalSteps,
1843
1866
  documentPath,
1844
1867
  verifyAttempts: state.verifyAttempts || 1,
1868
+ loadedKnowledge: state.loadedKnowledge?.map((k) => ({ filename: k.filename, content: k.content })),
1845
1869
  cwd
1846
1870
  });
1871
+ const onComplete = `npx spets orchestrate verify-done ${state.taskId}`;
1847
1872
  return {
1848
1873
  type: "phase",
1849
1874
  phase: "verify",
@@ -1859,7 +1884,8 @@ function buildPhaseVerifyResponse(cwd, state) {
1859
1884
  template: hasTemplate ? templatePath : void 0,
1860
1885
  document: documentPath
1861
1886
  },
1862
- onComplete: `npx spets orchestrate verify-done ${state.taskId}`
1887
+ onComplete,
1888
+ instructions: phaseWithJsonOutput(onComplete)
1863
1889
  };
1864
1890
  }
1865
1891
  function buildPhaseKnowledgeResponse(cwd, state) {
@@ -1872,6 +1898,7 @@ function buildPhaseKnowledgeResponse(cwd, state) {
1872
1898
  guide,
1873
1899
  cwd
1874
1900
  });
1901
+ const onComplete = `npx spets orchestrate knowledge-extract-done ${state.taskId}`;
1875
1902
  return {
1876
1903
  type: "phase",
1877
1904
  phase: "knowledge",
@@ -1884,21 +1911,31 @@ function buildPhaseKnowledgeResponse(cwd, state) {
1884
1911
  totalSteps: state.totalSteps
1885
1912
  },
1886
1913
  guide,
1887
- onComplete: `npx spets orchestrate knowledge-extract-done ${state.taskId}`
1914
+ onComplete,
1915
+ instructions: phaseWithJsonOutput(onComplete)
1888
1916
  };
1889
1917
  }
1890
1918
  function buildCheckpointClarifyResponse(state) {
1919
+ const onComplete = `npx spets orchestrate clarified ${state.taskId}`;
1891
1920
  return {
1892
1921
  type: "checkpoint",
1893
1922
  checkpoint: "clarify",
1894
1923
  taskId: state.taskId,
1895
1924
  step: state.currentStep,
1896
1925
  decisions: state.decisions || [],
1897
- onComplete: `npx spets orchestrate clarified ${state.taskId} '<answers_json>'`
1926
+ onComplete: `${onComplete} '<answers_json>'`,
1927
+ instructions: checkpointClarify(onComplete)
1898
1928
  };
1899
1929
  }
1900
1930
  function buildCheckpointApproveResponse(cwd, state) {
1901
1931
  const outputPath = getOutputPath(cwd);
1932
+ const specPath = join9(outputPath, state.taskId, `${state.currentStep}.md`);
1933
+ const cmds = {
1934
+ approve: `npx spets orchestrate approve ${state.taskId}`,
1935
+ revise: `npx spets orchestrate revise ${state.taskId} '<feedback>'`,
1936
+ reject: `npx spets orchestrate reject ${state.taskId}`,
1937
+ stop: `npx spets orchestrate stop ${state.taskId}`
1938
+ };
1902
1939
  return {
1903
1940
  type: "checkpoint",
1904
1941
  checkpoint: "approve",
@@ -1906,14 +1943,10 @@ function buildCheckpointApproveResponse(cwd, state) {
1906
1943
  step: state.currentStep,
1907
1944
  stepIndex: state.stepIndex,
1908
1945
  totalSteps: state.totalSteps,
1909
- specPath: join9(outputPath, state.taskId, `${state.currentStep}.md`),
1946
+ specPath,
1910
1947
  options: ["approve", "revise", "reject", "stop"],
1911
- onComplete: {
1912
- approve: `npx spets orchestrate approve ${state.taskId}`,
1913
- revise: `npx spets orchestrate revise ${state.taskId} '<feedback>'`,
1914
- reject: `npx spets orchestrate reject ${state.taskId}`,
1915
- stop: `npx spets orchestrate stop ${state.taskId}`
1916
- }
1948
+ onComplete: cmds,
1949
+ instructions: checkpointApprove(specPath, cmds)
1917
1950
  };
1918
1951
  }
1919
1952
  function buildCompleteResponse(cwd, state, status) {
@@ -1936,11 +1969,12 @@ function buildCompleteResponse(cwd, state, status) {
1936
1969
  status,
1937
1970
  taskId: state.taskId,
1938
1971
  outputs,
1939
- message: messages[status]
1972
+ message: messages[status],
1973
+ instructions: PRINT_MESSAGE_AND_STOP
1940
1974
  };
1941
1975
  }
1942
1976
  function buildErrorResponse(error, taskId, step) {
1943
- const response = { type: "error", error };
1977
+ const response = { type: "error", error, instructions: PRINT_ERROR_AND_STOP };
1944
1978
  if (taskId) response.taskId = taskId;
1945
1979
  if (step) response.step = step;
1946
1980
  return response;
@@ -2282,7 +2316,7 @@ var Orchestrator = class {
2282
2316
  const outputPath = getOutputPath(this.cwd);
2283
2317
  const tasks = [];
2284
2318
  if (!existsSync11(outputPath)) {
2285
- return { type: "list", tasks: [] };
2319
+ return { type: "list", tasks: [], instructions: LIST_EMPTY };
2286
2320
  }
2287
2321
  const dirs = readdirSync2(outputPath, { withFileTypes: true }).filter((d) => d.isDirectory());
2288
2322
  for (const dir of dirs) {
@@ -2304,7 +2338,11 @@ var Orchestrator = class {
2304
2338
  const bTime = b.updatedAt ? new Date(b.updatedAt).getTime() : 0;
2305
2339
  return bTime - aTime;
2306
2340
  });
2307
- return { type: "list", tasks };
2341
+ return {
2342
+ type: "list",
2343
+ tasks,
2344
+ instructions: tasks.length > 0 ? LIST_TASKS : LIST_EMPTY
2345
+ };
2308
2346
  }
2309
2347
  /**
2310
2348
  * Get current workflow status
@@ -2446,7 +2484,8 @@ function parseKnowledgeOutput(response) {
2446
2484
  entries: entries.map((e) => ({
2447
2485
  filename: e.filename || "",
2448
2486
  content: e.content || "",
2449
- reason: e.reason || ""
2487
+ reason: e.reason || "",
2488
+ action: e.action === "update" ? "update" : "create"
2450
2489
  }))
2451
2490
  };
2452
2491
  }
@@ -2458,7 +2497,8 @@ function parseKnowledgeOutput(response) {
2458
2497
  entries: parsed.map((e) => ({
2459
2498
  filename: e.filename || "",
2460
2499
  content: e.content || "",
2461
- reason: e.reason || ""
2500
+ reason: e.reason || "",
2501
+ action: e.action === "update" ? "update" : "create"
2462
2502
  }))
2463
2503
  };
2464
2504
  }
@@ -5861,7 +5901,7 @@ async function uiKnowledgeCommand(entryName, options) {
5861
5901
  console.log(renderKnowledgeJSON(void 0, cwd));
5862
5902
  return;
5863
5903
  }
5864
- const { runKnowledgeInteractive: runKnowledgeInteractive2 } = await import("./knowledge-QC6464NS.js");
5904
+ const { runKnowledgeInteractive: runKnowledgeInteractive2 } = await import("./knowledge-TVXZATJX.js");
5865
5905
  await runKnowledgeInteractive2(cwd);
5866
5906
  }
5867
5907
 
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  renderKnowledgeJSON,
3
3
  runKnowledgeInteractive
4
- } from "./chunk-3QH66XVU.js";
4
+ } from "./chunk-OIFVTELS.js";
5
5
  import "./chunk-2MUV3F53.js";
6
6
  export {
7
7
  renderKnowledgeJSON,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spets",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "Spec Driven Development Execution Framework",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -27,6 +27,12 @@
27
27
  - `pattern--` : 코드 패턴 (예: `pattern--error-handling`)
28
28
  - prefix 없이 사용해도 무방
29
29
 
30
+ ## Update or Create
31
+
32
+ - 기존 파일과 겹치는 내용이면 `"action": "update"`로 업데이트
33
+ - 새로운 지식이면 `"action": "create"`로 생성
34
+ - 중복 파일이 누적되지 않도록 기존 항목을 확인하세요
35
+
30
36
  ## 예시
31
37
 
32
38
  ```