@anthropologies/claudestory 0.1.32 → 0.1.34

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.
package/dist/cli.js CHANGED
@@ -298,7 +298,9 @@ var init_config = __esm({
298
298
  recipeOverrides: z7.object({
299
299
  maxTicketsPerSession: z7.number().min(0).optional(),
300
300
  compactThreshold: z7.string().optional(),
301
- reviewBackends: z7.array(z7.string()).optional()
301
+ reviewBackends: z7.array(z7.string()).optional(),
302
+ handoverInterval: z7.number().min(0).optional(),
303
+ stages: z7.record(z7.record(z7.unknown())).optional()
302
304
  }).optional()
303
305
  }).passthrough();
304
306
  }
@@ -5021,6 +5023,7 @@ var init_session_types = __esm({
5021
5023
  "WRITE_TESTS",
5022
5024
  "TEST",
5023
5025
  "CODE_REVIEW",
5026
+ "BUILD",
5024
5027
  "VERIFY",
5025
5028
  "FINALIZE",
5026
5029
  "COMPACT",
@@ -5046,6 +5049,7 @@ var init_session_types = __esm({
5046
5049
  "WRITE_TESTS",
5047
5050
  "TEST",
5048
5051
  "CODE_REVIEW",
5052
+ "BUILD",
5049
5053
  "VERIFY",
5050
5054
  "FINALIZE",
5051
5055
  "COMPACT",
@@ -5196,6 +5200,7 @@ var init_session_types = __esm({
5196
5200
  }).nullable().default(null),
5197
5201
  testRetryCount: z9.number().default(0),
5198
5202
  writeTestsRetryCount: z9.number().default(0),
5203
+ buildRetryCount: z9.number().default(0),
5199
5204
  verifyRetryCount: z9.number().default(0),
5200
5205
  verifyAutoDetected: z9.boolean().default(false),
5201
5206
  // T-128: Resolved recipe (frozen at session start, survives compact/resume)
@@ -5901,7 +5906,19 @@ function resolveRecipe(recipeName, projectOverrides) {
5901
5906
  }
5902
5907
  }
5903
5908
  let pipeline = raw.pipeline ? [...raw.pipeline] : [...DEFAULT_PIPELINE];
5904
- const stages2 = raw.stages ?? {};
5909
+ const recipeStages = raw.stages ?? {};
5910
+ const stageOverrides = projectOverrides?.stages ?? {};
5911
+ const stages2 = {};
5912
+ for (const [key, value] of Object.entries(recipeStages)) {
5913
+ const override = stageOverrides[key];
5914
+ const safeOverride = override && typeof override === "object" && !Array.isArray(override) ? override : {};
5915
+ stages2[key] = { ...value, ...safeOverride };
5916
+ }
5917
+ for (const [key, value] of Object.entries(stageOverrides)) {
5918
+ if (!stages2[key] && value && typeof value === "object" && !Array.isArray(value)) {
5919
+ stages2[key] = { ...value };
5920
+ }
5921
+ }
5905
5922
  if (stages2.WRITE_TESTS?.enabled) {
5906
5923
  const implementIdx = pipeline.indexOf("IMPLEMENT");
5907
5924
  if (implementIdx !== -1 && !pipeline.includes("WRITE_TESTS")) {
@@ -5914,6 +5931,12 @@ function resolveRecipe(recipeName, projectOverrides) {
5914
5931
  pipeline.splice(codeReviewIdx + 1, 0, "VERIFY");
5915
5932
  }
5916
5933
  }
5934
+ if (stages2.BUILD?.enabled) {
5935
+ const finalizeIdx = pipeline.indexOf("FINALIZE");
5936
+ if (finalizeIdx !== -1 && !pipeline.includes("BUILD")) {
5937
+ pipeline.splice(finalizeIdx, 0, "BUILD");
5938
+ }
5939
+ }
5917
5940
  if (stages2.TEST?.enabled) {
5918
5941
  const implementIdx = pipeline.indexOf("IMPLEMENT");
5919
5942
  if (implementIdx !== -1 && !pipeline.includes("TEST")) {
@@ -6526,7 +6549,8 @@ var init_implement = __esm({
6526
6549
  ].join("\n"),
6527
6550
  reminders: [
6528
6551
  "Follow the plan exactly. Do NOT deviate without re-planning.",
6529
- "Do NOT ask the user for confirmation."
6552
+ "Do NOT ask the user for confirmation.",
6553
+ "If you discover pre-existing bugs, failing tests not caused by your changes, or other out-of-scope problems, file them as issues using claudestory_issue_create. Do not fix them inline."
6530
6554
  ],
6531
6555
  transitionedFrom: ctx.state.previousState ?? void 0
6532
6556
  };
@@ -6804,12 +6828,16 @@ var init_code_review = __esm({
6804
6828
  "",
6805
6829
  `Capture the diff with: ${diffCommand}`,
6806
6830
  "",
6807
- "**IMPORTANT:** Pass the FULL unified diff output to the reviewer. Do NOT summarize, compress, or truncate the diff.",
6831
+ "**IMPORTANT:** Pass the FULL unified diff to the reviewer. For diffs over ~500 lines, use file-scoped chunks (`git diff <mergebase> -- <filepath>`) across separate calls (pass the same session_id). Do NOT summarize or truncate any individual chunk.",
6808
6832
  "",
6809
6833
  `Run a code review using **${reviewer}**.`,
6810
6834
  "When done, report verdict and findings."
6811
6835
  ].join("\n"),
6812
- reminders: [diffReminder, "Do NOT compress or summarize the diff."],
6836
+ reminders: [
6837
+ diffReminder,
6838
+ "Do NOT compress or summarize the diff.",
6839
+ "If the reviewer flags pre-existing issues unrelated to your changes, file them as issues using claudestory_issue_create with severity and impact. Do not fix them in this ticket."
6840
+ ],
6813
6841
  transitionedFrom: ctx.state.previousState ?? void 0
6814
6842
  };
6815
6843
  }
@@ -6924,6 +6952,95 @@ var init_code_review = __esm({
6924
6952
  }
6925
6953
  });
6926
6954
 
6955
+ // src/autonomous/stages/build.ts
6956
+ var MAX_BUILD_RETRIES, BuildStage;
6957
+ var init_build = __esm({
6958
+ "src/autonomous/stages/build.ts"() {
6959
+ "use strict";
6960
+ init_esm_shims();
6961
+ MAX_BUILD_RETRIES = 2;
6962
+ BuildStage = class {
6963
+ id = "BUILD";
6964
+ skip(ctx) {
6965
+ const buildConfig = ctx.recipe.stages?.BUILD;
6966
+ return !buildConfig?.enabled;
6967
+ }
6968
+ async enter(ctx) {
6969
+ const buildConfig = ctx.recipe.stages?.BUILD;
6970
+ const command = buildConfig?.command ?? "npm run build";
6971
+ const retryCount = ctx.state.buildRetryCount ?? 0;
6972
+ return {
6973
+ instruction: [
6974
+ `# Build${retryCount > 0 ? ` (retry ${retryCount}/${MAX_BUILD_RETRIES})` : ""}`,
6975
+ "",
6976
+ `Run the build: \`${command}\``,
6977
+ "",
6978
+ "Report the results with:",
6979
+ "```json",
6980
+ `{ "sessionId": "${ctx.state.sessionId}", "action": "report", "report": { "completedAction": "build_done", "notes": "<exit code and summary>" } }`,
6981
+ "```",
6982
+ "",
6983
+ "Include the exit code (0 = pass, non-0 = fail) and any error output in notes."
6984
+ ].join("\n"),
6985
+ reminders: ["Run the FULL build, not a partial or dev-mode build."],
6986
+ transitionedFrom: ctx.state.previousState ?? void 0
6987
+ };
6988
+ }
6989
+ async report(ctx, report) {
6990
+ const notes = report.notes ?? "";
6991
+ const retryCount = ctx.state.buildRetryCount ?? 0;
6992
+ const exitCodeMatch = notes.match(/exit\s*(?:code[:\s]*)?\s*(\d+)/i);
6993
+ if (!exitCodeMatch) {
6994
+ const nextRetry = retryCount + 1;
6995
+ if (nextRetry > MAX_BUILD_RETRIES) {
6996
+ ctx.writeState({ buildRetryCount: 0 });
6997
+ ctx.appendEvent("build_parse_exhausted", { retryCount: nextRetry });
6998
+ return {
6999
+ action: "advance",
7000
+ result: {
7001
+ instruction: `Could not parse build exit code after ${MAX_BUILD_RETRIES} retries. Proceeding, but build status is unknown.`,
7002
+ reminders: ["Mention unknown build status in the commit message."]
7003
+ }
7004
+ };
7005
+ }
7006
+ ctx.writeState({ buildRetryCount: nextRetry });
7007
+ return { action: "retry", instruction: 'Could not parse exit code from notes. Include "exit code: 0" (or non-zero) in your notes.' };
7008
+ }
7009
+ const exitCode = parseInt(exitCodeMatch[1], 10);
7010
+ if (exitCode === 0) {
7011
+ ctx.writeState({ buildRetryCount: 0 });
7012
+ ctx.appendEvent("build_passed", { retryCount, notes: notes.slice(0, 200) });
7013
+ return { action: "advance" };
7014
+ }
7015
+ if (retryCount < MAX_BUILD_RETRIES) {
7016
+ ctx.writeState({ buildRetryCount: retryCount + 1 });
7017
+ ctx.appendEvent("build_failed_retry", { retryCount: retryCount + 1, notes: notes.slice(0, 200) });
7018
+ return {
7019
+ action: "back",
7020
+ target: "IMPLEMENT",
7021
+ reason: `Build failed (attempt ${retryCount + 1}/${MAX_BUILD_RETRIES}). Fix the build errors.`
7022
+ };
7023
+ }
7024
+ ctx.writeState({ buildRetryCount: 0 });
7025
+ ctx.appendEvent("build_failed_exhausted", { retryCount, notes: notes.slice(0, 200) });
7026
+ return {
7027
+ action: "advance",
7028
+ result: {
7029
+ instruction: [
7030
+ "# Build Failed - Proceeding",
7031
+ "",
7032
+ `Build failed after ${MAX_BUILD_RETRIES} retries. Proceeding but build errors remain.`,
7033
+ "",
7034
+ "Document the build failure in the commit message."
7035
+ ].join("\n"),
7036
+ reminders: ["Mention build failure in the commit message."]
7037
+ }
7038
+ };
7039
+ }
7040
+ };
7041
+ }
7042
+ });
7043
+
6927
7044
  // src/autonomous/stages/verify.ts
6928
7045
  function exhaustionAction2(ctx) {
6929
7046
  ctx.writeState({ verifyRetryCount: 0 });
@@ -7733,6 +7850,7 @@ var init_stages = __esm({
7733
7850
  init_write_tests();
7734
7851
  init_test();
7735
7852
  init_code_review();
7853
+ init_build();
7736
7854
  init_verify();
7737
7855
  init_finalize();
7738
7856
  init_complete();
@@ -7746,6 +7864,7 @@ var init_stages = __esm({
7746
7864
  registerStage(new WriteTestsStage());
7747
7865
  registerStage(new TestStage());
7748
7866
  registerStage(new CodeReviewStage());
7867
+ registerStage(new BuildStage());
7749
7868
  registerStage(new VerifyStage());
7750
7869
  registerStage(new FinalizeStage());
7751
7870
  registerStage(new CompleteStage());
@@ -7954,6 +8073,9 @@ async function handleStart(root, args) {
7954
8073
  if (typeof overrides.compactThreshold === "string") sessionConfig.compactThreshold = overrides.compactThreshold;
7955
8074
  if (Array.isArray(overrides.reviewBackends)) sessionConfig.reviewBackends = overrides.reviewBackends;
7956
8075
  if (typeof overrides.handoverInterval === "number") sessionConfig.handoverInterval = overrides.handoverInterval;
8076
+ if (overrides.stages && typeof overrides.stages === "object") {
8077
+ sessionConfig.stageOverrides = overrides.stages;
8078
+ }
7957
8079
  }
7958
8080
  } catch {
7959
8081
  }
@@ -7963,7 +8085,8 @@ async function handleStart(root, args) {
7963
8085
  const resolvedRecipe = resolveRecipe(recipe, {
7964
8086
  maxTicketsPerSession: sessionConfig.maxTicketsPerSession,
7965
8087
  compactThreshold: sessionConfig.compactThreshold,
7966
- reviewBackends: sessionConfig.reviewBackends
8088
+ reviewBackends: sessionConfig.reviewBackends,
8089
+ stages: sessionConfig.stageOverrides
7967
8090
  });
7968
8091
  const session = createSession(root, recipe, wsId, sessionConfig);
7969
8092
  const dir = sessionDir(root, session.sessionId);
@@ -8763,14 +8886,6 @@ async function handleCancel(root, args) {
8763
8886
  if (info.state.state === "SESSION_END" || info.state.status === "completed") {
8764
8887
  return guideError(new Error("Session already ended."));
8765
8888
  }
8766
- const CANCELLABLE_STATES = /* @__PURE__ */ new Set(["PICK_TICKET", "COMPLETE", "HANDOVER"]);
8767
- if (info.state.recipe === "coding" && !CANCELLABLE_STATES.has(info.state.state)) {
8768
- const sessionMode = info.state.mode ?? "auto";
8769
- const modeGuidance = sessionMode === "plan" ? "Plan mode sessions end after plan review approval \u2014 continue to that step." : sessionMode === "review" ? "Review mode sessions end after code review approval \u2014 continue to that step." : sessionMode === "guided" ? "Guided mode sessions end after ticket completion \u2014 continue to FINALIZE." : "Complete the current ticket and write a handover to end the session.";
8770
- return guideError(new Error(
8771
- `Cannot cancel a coding session from ${info.state.state}. ${modeGuidance}`
8772
- ));
8773
- }
8774
8889
  await recoverPendingMutation(info.dir, info.state, root);
8775
8890
  const cancelInfo = findSessionById(root, args.sessionId) ?? info;
8776
8891
  let ticketReleased = false;
@@ -10258,7 +10373,7 @@ var init_mcp = __esm({
10258
10373
  init_init();
10259
10374
  ENV_VAR2 = "CLAUDESTORY_PROJECT_ROOT";
10260
10375
  CONFIG_PATH2 = ".story/config.json";
10261
- version = "0.1.32";
10376
+ version = "0.1.34";
10262
10377
  main().catch((err) => {
10263
10378
  process.stderr.write(`Fatal: ${err instanceof Error ? err.message : String(err)}
10264
10379
  `);
@@ -13542,7 +13657,7 @@ async function runCli() {
13542
13657
  registerConfigCommand: registerConfigCommand2,
13543
13658
  registerSessionCommand: registerSessionCommand2
13544
13659
  } = await Promise.resolve().then(() => (init_register(), register_exports));
13545
- const version2 = "0.1.32";
13660
+ const version2 = "0.1.34";
13546
13661
  class HandledError extends Error {
13547
13662
  constructor() {
13548
13663
  super("HANDLED_ERROR");
package/dist/index.d.ts CHANGED
@@ -348,14 +348,20 @@ declare const ConfigSchema: z.ZodObject<{
348
348
  maxTicketsPerSession: z.ZodOptional<z.ZodNumber>;
349
349
  compactThreshold: z.ZodOptional<z.ZodString>;
350
350
  reviewBackends: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
351
+ handoverInterval: z.ZodOptional<z.ZodNumber>;
352
+ stages: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodRecord<z.ZodString, z.ZodUnknown>>>;
351
353
  }, "strip", z.ZodTypeAny, {
352
354
  maxTicketsPerSession?: number | undefined;
353
355
  compactThreshold?: string | undefined;
354
356
  reviewBackends?: string[] | undefined;
357
+ handoverInterval?: number | undefined;
358
+ stages?: Record<string, Record<string, unknown>> | undefined;
355
359
  }, {
356
360
  maxTicketsPerSession?: number | undefined;
357
361
  compactThreshold?: string | undefined;
358
362
  reviewBackends?: string[] | undefined;
363
+ handoverInterval?: number | undefined;
364
+ stages?: Record<string, Record<string, unknown>> | undefined;
359
365
  }>>;
360
366
  }, "passthrough", z.ZodTypeAny, z.objectOutputType<{
361
367
  version: z.ZodNumber;
@@ -387,14 +393,20 @@ declare const ConfigSchema: z.ZodObject<{
387
393
  maxTicketsPerSession: z.ZodOptional<z.ZodNumber>;
388
394
  compactThreshold: z.ZodOptional<z.ZodString>;
389
395
  reviewBackends: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
396
+ handoverInterval: z.ZodOptional<z.ZodNumber>;
397
+ stages: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodRecord<z.ZodString, z.ZodUnknown>>>;
390
398
  }, "strip", z.ZodTypeAny, {
391
399
  maxTicketsPerSession?: number | undefined;
392
400
  compactThreshold?: string | undefined;
393
401
  reviewBackends?: string[] | undefined;
402
+ handoverInterval?: number | undefined;
403
+ stages?: Record<string, Record<string, unknown>> | undefined;
394
404
  }, {
395
405
  maxTicketsPerSession?: number | undefined;
396
406
  compactThreshold?: string | undefined;
397
407
  reviewBackends?: string[] | undefined;
408
+ handoverInterval?: number | undefined;
409
+ stages?: Record<string, Record<string, unknown>> | undefined;
398
410
  }>>;
399
411
  }, z.ZodTypeAny, "passthrough">, z.objectInputType<{
400
412
  version: z.ZodNumber;
@@ -426,14 +438,20 @@ declare const ConfigSchema: z.ZodObject<{
426
438
  maxTicketsPerSession: z.ZodOptional<z.ZodNumber>;
427
439
  compactThreshold: z.ZodOptional<z.ZodString>;
428
440
  reviewBackends: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
441
+ handoverInterval: z.ZodOptional<z.ZodNumber>;
442
+ stages: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodRecord<z.ZodString, z.ZodUnknown>>>;
429
443
  }, "strip", z.ZodTypeAny, {
430
444
  maxTicketsPerSession?: number | undefined;
431
445
  compactThreshold?: string | undefined;
432
446
  reviewBackends?: string[] | undefined;
447
+ handoverInterval?: number | undefined;
448
+ stages?: Record<string, Record<string, unknown>> | undefined;
433
449
  }, {
434
450
  maxTicketsPerSession?: number | undefined;
435
451
  compactThreshold?: string | undefined;
436
452
  reviewBackends?: string[] | undefined;
453
+ handoverInterval?: number | undefined;
454
+ stages?: Record<string, Record<string, unknown>> | undefined;
437
455
  }>>;
438
456
  }, z.ZodTypeAny, "passthrough">>;
439
457
  type Config = z.infer<typeof ConfigSchema>;
@@ -950,14 +968,20 @@ declare const SnapshotV1Schema: z.ZodObject<{
950
968
  maxTicketsPerSession: z.ZodOptional<z.ZodNumber>;
951
969
  compactThreshold: z.ZodOptional<z.ZodString>;
952
970
  reviewBackends: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
971
+ handoverInterval: z.ZodOptional<z.ZodNumber>;
972
+ stages: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodRecord<z.ZodString, z.ZodUnknown>>>;
953
973
  }, "strip", z.ZodTypeAny, {
954
974
  maxTicketsPerSession?: number | undefined;
955
975
  compactThreshold?: string | undefined;
956
976
  reviewBackends?: string[] | undefined;
977
+ handoverInterval?: number | undefined;
978
+ stages?: Record<string, Record<string, unknown>> | undefined;
957
979
  }, {
958
980
  maxTicketsPerSession?: number | undefined;
959
981
  compactThreshold?: string | undefined;
960
982
  reviewBackends?: string[] | undefined;
983
+ handoverInterval?: number | undefined;
984
+ stages?: Record<string, Record<string, unknown>> | undefined;
961
985
  }>>;
962
986
  }, "passthrough", z.ZodTypeAny, z.objectOutputType<{
963
987
  version: z.ZodNumber;
@@ -989,14 +1013,20 @@ declare const SnapshotV1Schema: z.ZodObject<{
989
1013
  maxTicketsPerSession: z.ZodOptional<z.ZodNumber>;
990
1014
  compactThreshold: z.ZodOptional<z.ZodString>;
991
1015
  reviewBackends: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
1016
+ handoverInterval: z.ZodOptional<z.ZodNumber>;
1017
+ stages: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodRecord<z.ZodString, z.ZodUnknown>>>;
992
1018
  }, "strip", z.ZodTypeAny, {
993
1019
  maxTicketsPerSession?: number | undefined;
994
1020
  compactThreshold?: string | undefined;
995
1021
  reviewBackends?: string[] | undefined;
1022
+ handoverInterval?: number | undefined;
1023
+ stages?: Record<string, Record<string, unknown>> | undefined;
996
1024
  }, {
997
1025
  maxTicketsPerSession?: number | undefined;
998
1026
  compactThreshold?: string | undefined;
999
1027
  reviewBackends?: string[] | undefined;
1028
+ handoverInterval?: number | undefined;
1029
+ stages?: Record<string, Record<string, unknown>> | undefined;
1000
1030
  }>>;
1001
1031
  }, z.ZodTypeAny, "passthrough">, z.objectInputType<{
1002
1032
  version: z.ZodNumber;
@@ -1028,14 +1058,20 @@ declare const SnapshotV1Schema: z.ZodObject<{
1028
1058
  maxTicketsPerSession: z.ZodOptional<z.ZodNumber>;
1029
1059
  compactThreshold: z.ZodOptional<z.ZodString>;
1030
1060
  reviewBackends: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
1061
+ handoverInterval: z.ZodOptional<z.ZodNumber>;
1062
+ stages: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodRecord<z.ZodString, z.ZodUnknown>>>;
1031
1063
  }, "strip", z.ZodTypeAny, {
1032
1064
  maxTicketsPerSession?: number | undefined;
1033
1065
  compactThreshold?: string | undefined;
1034
1066
  reviewBackends?: string[] | undefined;
1067
+ handoverInterval?: number | undefined;
1068
+ stages?: Record<string, Record<string, unknown>> | undefined;
1035
1069
  }, {
1036
1070
  maxTicketsPerSession?: number | undefined;
1037
1071
  compactThreshold?: string | undefined;
1038
1072
  reviewBackends?: string[] | undefined;
1073
+ handoverInterval?: number | undefined;
1074
+ stages?: Record<string, Record<string, unknown>> | undefined;
1039
1075
  }>>;
1040
1076
  }, z.ZodTypeAny, "passthrough">>;
1041
1077
  roadmap: z.ZodObject<{
@@ -1444,6 +1480,8 @@ declare const SnapshotV1Schema: z.ZodObject<{
1444
1480
  maxTicketsPerSession?: number | undefined;
1445
1481
  compactThreshold?: string | undefined;
1446
1482
  reviewBackends?: string[] | undefined;
1483
+ handoverInterval?: number | undefined;
1484
+ stages?: Record<string, Record<string, unknown>> | undefined;
1447
1485
  } | undefined;
1448
1486
  } & {
1449
1487
  [k: string]: unknown;
@@ -1533,6 +1571,8 @@ declare const SnapshotV1Schema: z.ZodObject<{
1533
1571
  maxTicketsPerSession?: number | undefined;
1534
1572
  compactThreshold?: string | undefined;
1535
1573
  reviewBackends?: string[] | undefined;
1574
+ handoverInterval?: number | undefined;
1575
+ stages?: Record<string, Record<string, unknown>> | undefined;
1536
1576
  } | undefined;
1537
1577
  } & {
1538
1578
  [k: string]: unknown;
package/dist/index.js CHANGED
@@ -140,7 +140,9 @@ var ConfigSchema = z6.object({
140
140
  recipeOverrides: z6.object({
141
141
  maxTicketsPerSession: z6.number().min(0).optional(),
142
142
  compactThreshold: z6.string().optional(),
143
- reviewBackends: z6.array(z6.string()).optional()
143
+ reviewBackends: z6.array(z6.string()).optional(),
144
+ handoverInterval: z6.number().min(0).optional(),
145
+ stages: z6.record(z6.record(z6.unknown())).optional()
144
146
  }).optional()
145
147
  }).passthrough();
146
148
 
package/dist/mcp.js CHANGED
@@ -240,7 +240,9 @@ var init_config = __esm({
240
240
  recipeOverrides: z7.object({
241
241
  maxTicketsPerSession: z7.number().min(0).optional(),
242
242
  compactThreshold: z7.string().optional(),
243
- reviewBackends: z7.array(z7.string()).optional()
243
+ reviewBackends: z7.array(z7.string()).optional(),
244
+ handoverInterval: z7.number().min(0).optional(),
245
+ stages: z7.record(z7.record(z7.unknown())).optional()
244
246
  }).optional()
245
247
  }).passthrough();
246
248
  }
@@ -3488,6 +3490,7 @@ var init_session_types = __esm({
3488
3490
  "WRITE_TESTS",
3489
3491
  "TEST",
3490
3492
  "CODE_REVIEW",
3493
+ "BUILD",
3491
3494
  "VERIFY",
3492
3495
  "FINALIZE",
3493
3496
  "COMPACT",
@@ -3638,6 +3641,7 @@ var init_session_types = __esm({
3638
3641
  }).nullable().default(null),
3639
3642
  testRetryCount: z9.number().default(0),
3640
3643
  writeTestsRetryCount: z9.number().default(0),
3644
+ buildRetryCount: z9.number().default(0),
3641
3645
  verifyRetryCount: z9.number().default(0),
3642
3646
  verifyAutoDetected: z9.boolean().default(false),
3643
3647
  // T-128: Resolved recipe (frozen at session start, survives compact/resume)
@@ -5510,7 +5514,19 @@ function resolveRecipe(recipeName, projectOverrides) {
5510
5514
  }
5511
5515
  }
5512
5516
  let pipeline = raw.pipeline ? [...raw.pipeline] : [...DEFAULT_PIPELINE];
5513
- const stages2 = raw.stages ?? {};
5517
+ const recipeStages = raw.stages ?? {};
5518
+ const stageOverrides = projectOverrides?.stages ?? {};
5519
+ const stages2 = {};
5520
+ for (const [key, value] of Object.entries(recipeStages)) {
5521
+ const override = stageOverrides[key];
5522
+ const safeOverride = override && typeof override === "object" && !Array.isArray(override) ? override : {};
5523
+ stages2[key] = { ...value, ...safeOverride };
5524
+ }
5525
+ for (const [key, value] of Object.entries(stageOverrides)) {
5526
+ if (!stages2[key] && value && typeof value === "object" && !Array.isArray(value)) {
5527
+ stages2[key] = { ...value };
5528
+ }
5529
+ }
5514
5530
  if (stages2.WRITE_TESTS?.enabled) {
5515
5531
  const implementIdx = pipeline.indexOf("IMPLEMENT");
5516
5532
  if (implementIdx !== -1 && !pipeline.includes("WRITE_TESTS")) {
@@ -5523,6 +5539,12 @@ function resolveRecipe(recipeName, projectOverrides) {
5523
5539
  pipeline.splice(codeReviewIdx + 1, 0, "VERIFY");
5524
5540
  }
5525
5541
  }
5542
+ if (stages2.BUILD?.enabled) {
5543
+ const finalizeIdx = pipeline.indexOf("FINALIZE");
5544
+ if (finalizeIdx !== -1 && !pipeline.includes("BUILD")) {
5545
+ pipeline.splice(finalizeIdx, 0, "BUILD");
5546
+ }
5547
+ }
5526
5548
  if (stages2.TEST?.enabled) {
5527
5549
  const implementIdx = pipeline.indexOf("IMPLEMENT");
5528
5550
  if (implementIdx !== -1 && !pipeline.includes("TEST")) {
@@ -6079,7 +6101,8 @@ var ImplementStage = class {
6079
6101
  ].join("\n"),
6080
6102
  reminders: [
6081
6103
  "Follow the plan exactly. Do NOT deviate without re-planning.",
6082
- "Do NOT ask the user for confirmation."
6104
+ "Do NOT ask the user for confirmation.",
6105
+ "If you discover pre-existing bugs, failing tests not caused by your changes, or other out-of-scope problems, file them as issues using claudestory_issue_create. Do not fix them inline."
6083
6106
  ],
6084
6107
  transitionedFrom: ctx.state.previousState ?? void 0
6085
6108
  };
@@ -6338,12 +6361,16 @@ var CodeReviewStage = class {
6338
6361
  "",
6339
6362
  `Capture the diff with: ${diffCommand}`,
6340
6363
  "",
6341
- "**IMPORTANT:** Pass the FULL unified diff output to the reviewer. Do NOT summarize, compress, or truncate the diff.",
6364
+ "**IMPORTANT:** Pass the FULL unified diff to the reviewer. For diffs over ~500 lines, use file-scoped chunks (`git diff <mergebase> -- <filepath>`) across separate calls (pass the same session_id). Do NOT summarize or truncate any individual chunk.",
6342
6365
  "",
6343
6366
  `Run a code review using **${reviewer}**.`,
6344
6367
  "When done, report verdict and findings."
6345
6368
  ].join("\n"),
6346
- reminders: [diffReminder, "Do NOT compress or summarize the diff."],
6369
+ reminders: [
6370
+ diffReminder,
6371
+ "Do NOT compress or summarize the diff.",
6372
+ "If the reviewer flags pre-existing issues unrelated to your changes, file them as issues using claudestory_issue_create with severity and impact. Do not fix them in this ticket."
6373
+ ],
6347
6374
  transitionedFrom: ctx.state.previousState ?? void 0
6348
6375
  };
6349
6376
  }
@@ -6456,6 +6483,89 @@ var CodeReviewStage = class {
6456
6483
  }
6457
6484
  };
6458
6485
 
6486
+ // src/autonomous/stages/build.ts
6487
+ init_esm_shims();
6488
+ var MAX_BUILD_RETRIES = 2;
6489
+ var BuildStage = class {
6490
+ id = "BUILD";
6491
+ skip(ctx) {
6492
+ const buildConfig = ctx.recipe.stages?.BUILD;
6493
+ return !buildConfig?.enabled;
6494
+ }
6495
+ async enter(ctx) {
6496
+ const buildConfig = ctx.recipe.stages?.BUILD;
6497
+ const command = buildConfig?.command ?? "npm run build";
6498
+ const retryCount = ctx.state.buildRetryCount ?? 0;
6499
+ return {
6500
+ instruction: [
6501
+ `# Build${retryCount > 0 ? ` (retry ${retryCount}/${MAX_BUILD_RETRIES})` : ""}`,
6502
+ "",
6503
+ `Run the build: \`${command}\``,
6504
+ "",
6505
+ "Report the results with:",
6506
+ "```json",
6507
+ `{ "sessionId": "${ctx.state.sessionId}", "action": "report", "report": { "completedAction": "build_done", "notes": "<exit code and summary>" } }`,
6508
+ "```",
6509
+ "",
6510
+ "Include the exit code (0 = pass, non-0 = fail) and any error output in notes."
6511
+ ].join("\n"),
6512
+ reminders: ["Run the FULL build, not a partial or dev-mode build."],
6513
+ transitionedFrom: ctx.state.previousState ?? void 0
6514
+ };
6515
+ }
6516
+ async report(ctx, report) {
6517
+ const notes = report.notes ?? "";
6518
+ const retryCount = ctx.state.buildRetryCount ?? 0;
6519
+ const exitCodeMatch = notes.match(/exit\s*(?:code[:\s]*)?\s*(\d+)/i);
6520
+ if (!exitCodeMatch) {
6521
+ const nextRetry = retryCount + 1;
6522
+ if (nextRetry > MAX_BUILD_RETRIES) {
6523
+ ctx.writeState({ buildRetryCount: 0 });
6524
+ ctx.appendEvent("build_parse_exhausted", { retryCount: nextRetry });
6525
+ return {
6526
+ action: "advance",
6527
+ result: {
6528
+ instruction: `Could not parse build exit code after ${MAX_BUILD_RETRIES} retries. Proceeding, but build status is unknown.`,
6529
+ reminders: ["Mention unknown build status in the commit message."]
6530
+ }
6531
+ };
6532
+ }
6533
+ ctx.writeState({ buildRetryCount: nextRetry });
6534
+ return { action: "retry", instruction: 'Could not parse exit code from notes. Include "exit code: 0" (or non-zero) in your notes.' };
6535
+ }
6536
+ const exitCode = parseInt(exitCodeMatch[1], 10);
6537
+ if (exitCode === 0) {
6538
+ ctx.writeState({ buildRetryCount: 0 });
6539
+ ctx.appendEvent("build_passed", { retryCount, notes: notes.slice(0, 200) });
6540
+ return { action: "advance" };
6541
+ }
6542
+ if (retryCount < MAX_BUILD_RETRIES) {
6543
+ ctx.writeState({ buildRetryCount: retryCount + 1 });
6544
+ ctx.appendEvent("build_failed_retry", { retryCount: retryCount + 1, notes: notes.slice(0, 200) });
6545
+ return {
6546
+ action: "back",
6547
+ target: "IMPLEMENT",
6548
+ reason: `Build failed (attempt ${retryCount + 1}/${MAX_BUILD_RETRIES}). Fix the build errors.`
6549
+ };
6550
+ }
6551
+ ctx.writeState({ buildRetryCount: 0 });
6552
+ ctx.appendEvent("build_failed_exhausted", { retryCount, notes: notes.slice(0, 200) });
6553
+ return {
6554
+ action: "advance",
6555
+ result: {
6556
+ instruction: [
6557
+ "# Build Failed - Proceeding",
6558
+ "",
6559
+ `Build failed after ${MAX_BUILD_RETRIES} retries. Proceeding but build errors remain.`,
6560
+ "",
6561
+ "Document the build failure in the commit message."
6562
+ ].join("\n"),
6563
+ reminders: ["Mention build failure in the commit message."]
6564
+ }
6565
+ };
6566
+ }
6567
+ };
6568
+
6459
6569
  // src/autonomous/stages/verify.ts
6460
6570
  init_esm_shims();
6461
6571
  var MAX_VERIFY_RETRIES = 3;
@@ -7219,6 +7329,7 @@ registerStage(new ImplementStage());
7219
7329
  registerStage(new WriteTestsStage());
7220
7330
  registerStage(new TestStage());
7221
7331
  registerStage(new CodeReviewStage());
7332
+ registerStage(new BuildStage());
7222
7333
  registerStage(new VerifyStage());
7223
7334
  registerStage(new FinalizeStage());
7224
7335
  registerStage(new CompleteStage());
@@ -7434,6 +7545,9 @@ async function handleStart(root, args) {
7434
7545
  if (typeof overrides.compactThreshold === "string") sessionConfig.compactThreshold = overrides.compactThreshold;
7435
7546
  if (Array.isArray(overrides.reviewBackends)) sessionConfig.reviewBackends = overrides.reviewBackends;
7436
7547
  if (typeof overrides.handoverInterval === "number") sessionConfig.handoverInterval = overrides.handoverInterval;
7548
+ if (overrides.stages && typeof overrides.stages === "object") {
7549
+ sessionConfig.stageOverrides = overrides.stages;
7550
+ }
7437
7551
  }
7438
7552
  } catch {
7439
7553
  }
@@ -7443,7 +7557,8 @@ async function handleStart(root, args) {
7443
7557
  const resolvedRecipe = resolveRecipe(recipe, {
7444
7558
  maxTicketsPerSession: sessionConfig.maxTicketsPerSession,
7445
7559
  compactThreshold: sessionConfig.compactThreshold,
7446
- reviewBackends: sessionConfig.reviewBackends
7560
+ reviewBackends: sessionConfig.reviewBackends,
7561
+ stages: sessionConfig.stageOverrides
7447
7562
  });
7448
7563
  const session = createSession(root, recipe, wsId, sessionConfig);
7449
7564
  const dir = sessionDir(root, session.sessionId);
@@ -8244,14 +8359,6 @@ async function handleCancel(root, args) {
8244
8359
  if (info.state.state === "SESSION_END" || info.state.status === "completed") {
8245
8360
  return guideError(new Error("Session already ended."));
8246
8361
  }
8247
- const CANCELLABLE_STATES = /* @__PURE__ */ new Set(["PICK_TICKET", "COMPLETE", "HANDOVER"]);
8248
- if (info.state.recipe === "coding" && !CANCELLABLE_STATES.has(info.state.state)) {
8249
- const sessionMode = info.state.mode ?? "auto";
8250
- const modeGuidance = sessionMode === "plan" ? "Plan mode sessions end after plan review approval \u2014 continue to that step." : sessionMode === "review" ? "Review mode sessions end after code review approval \u2014 continue to that step." : sessionMode === "guided" ? "Guided mode sessions end after ticket completion \u2014 continue to FINALIZE." : "Complete the current ticket and write a handover to end the session.";
8251
- return guideError(new Error(
8252
- `Cannot cancel a coding session from ${info.state.state}. ${modeGuidance}`
8253
- ));
8254
- }
8255
8362
  await recoverPendingMutation(info.dir, info.state, root);
8256
8363
  const cancelInfo = findSessionById(root, args.sessionId) ?? info;
8257
8364
  let ticketReleased = false;
@@ -9414,7 +9521,7 @@ async function ensureGitignoreEntries(gitignorePath, entries) {
9414
9521
  // src/mcp/index.ts
9415
9522
  var ENV_VAR2 = "CLAUDESTORY_PROJECT_ROOT";
9416
9523
  var CONFIG_PATH2 = ".story/config.json";
9417
- var version = "0.1.32";
9524
+ var version = "0.1.34";
9418
9525
  function tryDiscoverRoot() {
9419
9526
  const envRoot = process.env[ENV_VAR2];
9420
9527
  if (envRoot) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@anthropologies/claudestory",
3
- "version": "0.1.32",
3
+ "version": "0.1.34",
4
4
  "license": "UNLICENSED",
5
5
  "description": "Cross-session context persistence for AI coding projects. Tracks tickets, issues, roadmap, and handovers so every session builds on the last.",
6
6
  "keywords": [