@anthropologies/claudestory 0.1.27 → 0.1.29
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 +117 -7
- package/dist/index.d.ts +7 -2
- package/dist/index.js +24 -2
- package/dist/mcp.js +107 -6
- package/package.json +1 -1
- package/src/skill/SKILL.md +8 -0
package/dist/cli.js
CHANGED
|
@@ -491,6 +491,15 @@ var init_project_state = __esm({
|
|
|
491
491
|
get blockedCount() {
|
|
492
492
|
return this.leafTickets.filter((t) => t.status !== "complete" && this.isBlocked(t)).length;
|
|
493
493
|
}
|
|
494
|
+
/** True when the project has been initialized but not yet populated with tickets/issues/handovers. */
|
|
495
|
+
get isEmptyScaffold() {
|
|
496
|
+
return this.tickets.length === 0 && this.issues.length === 0 && this.handoverFilenames.length === 0 && this.isDefaultScaffoldPhases;
|
|
497
|
+
}
|
|
498
|
+
get isDefaultScaffoldPhases() {
|
|
499
|
+
const { phases } = this.roadmap;
|
|
500
|
+
if (phases.length === 0) return true;
|
|
501
|
+
return phases.length === 1 && phases[0].id === "p0";
|
|
502
|
+
}
|
|
494
503
|
ticketByID(id) {
|
|
495
504
|
return this.ticketsByID.get(id);
|
|
496
505
|
}
|
|
@@ -1673,6 +1682,7 @@ var init_queries = __esm({
|
|
|
1673
1682
|
// src/core/output-formatter.ts
|
|
1674
1683
|
var output_formatter_exports = {};
|
|
1675
1684
|
__export(output_formatter_exports, {
|
|
1685
|
+
EMPTY_SCAFFOLD_HEADING: () => EMPTY_SCAFFOLD_HEADING,
|
|
1676
1686
|
ExitCode: () => ExitCode,
|
|
1677
1687
|
errorEnvelope: () => errorEnvelope,
|
|
1678
1688
|
escapeMarkdownInline: () => escapeMarkdownInline,
|
|
@@ -1763,6 +1773,7 @@ function formatStatus(state, format) {
|
|
|
1763
1773
|
activeLessons: state.activeLessonCount,
|
|
1764
1774
|
deprecatedLessons: state.deprecatedLessonCount,
|
|
1765
1775
|
handovers: state.handoverFilenames.length,
|
|
1776
|
+
isEmptyScaffold: state.isEmptyScaffold,
|
|
1766
1777
|
phases: phases.map((p) => ({
|
|
1767
1778
|
id: p.phase.id,
|
|
1768
1779
|
name: p.phase.name,
|
|
@@ -1790,6 +1801,13 @@ function formatStatus(state, format) {
|
|
|
1790
1801
|
const summary = p.phase.summary ?? truncate(p.phase.description, 80);
|
|
1791
1802
|
lines.push(`${indicator} **${escapeMarkdownInline(p.phase.name)}** (${p.leafCount} tickets) \u2014 ${escapeMarkdownInline(summary)}`);
|
|
1792
1803
|
}
|
|
1804
|
+
if (state.isEmptyScaffold) {
|
|
1805
|
+
lines.push("");
|
|
1806
|
+
lines.push(EMPTY_SCAFFOLD_HEADING);
|
|
1807
|
+
lines.push("");
|
|
1808
|
+
lines.push("This project has been initialized but has no tickets, issues, or handovers yet.");
|
|
1809
|
+
lines.push("Run the /story setup flow to analyze your project and create an initial roadmap.");
|
|
1810
|
+
}
|
|
1793
1811
|
return lines.join("\n");
|
|
1794
1812
|
}
|
|
1795
1813
|
function formatPhaseList(state, format) {
|
|
@@ -2629,11 +2647,14 @@ function formatReference(commands, mcpTools, format) {
|
|
|
2629
2647
|
lines.push("- **/story not available:** Run `claudestory setup-skill` to install the skill");
|
|
2630
2648
|
return lines.join("\n");
|
|
2631
2649
|
}
|
|
2632
|
-
function formatRecommendations(result, format) {
|
|
2650
|
+
function formatRecommendations(result, state, format) {
|
|
2633
2651
|
if (format === "json") {
|
|
2634
|
-
return JSON.stringify(successEnvelope(result), null, 2);
|
|
2652
|
+
return JSON.stringify(successEnvelope({ ...result, isEmptyScaffold: state.isEmptyScaffold }), null, 2);
|
|
2635
2653
|
}
|
|
2636
2654
|
if (result.recommendations.length === 0) {
|
|
2655
|
+
if (state.isEmptyScaffold) {
|
|
2656
|
+
return "No recommendations yet \u2014 this project needs tickets and phases. Run the /story setup flow to get started.";
|
|
2657
|
+
}
|
|
2637
2658
|
return "No recommendations \u2014 all work is complete or blocked.";
|
|
2638
2659
|
}
|
|
2639
2660
|
const lines = ["# Recommendations", ""];
|
|
@@ -2652,12 +2673,13 @@ function formatRecommendations(result, format) {
|
|
|
2652
2673
|
}
|
|
2653
2674
|
return lines.join("\n");
|
|
2654
2675
|
}
|
|
2655
|
-
var ExitCode;
|
|
2676
|
+
var EMPTY_SCAFFOLD_HEADING, ExitCode;
|
|
2656
2677
|
var init_output_formatter = __esm({
|
|
2657
2678
|
"src/core/output-formatter.ts"() {
|
|
2658
2679
|
"use strict";
|
|
2659
2680
|
init_esm_shims();
|
|
2660
2681
|
init_queries();
|
|
2682
|
+
EMPTY_SCAFFOLD_HEADING = "## Getting Started";
|
|
2661
2683
|
ExitCode = {
|
|
2662
2684
|
OK: 0,
|
|
2663
2685
|
USER_ERROR: 1,
|
|
@@ -4667,7 +4689,7 @@ var init_recommend = __esm({
|
|
|
4667
4689
|
// src/cli/commands/recommend.ts
|
|
4668
4690
|
function handleRecommend(ctx, count) {
|
|
4669
4691
|
const result = recommend(ctx.state, count);
|
|
4670
|
-
return { output: formatRecommendations(result, ctx.format) };
|
|
4692
|
+
return { output: formatRecommendations(result, ctx.state, ctx.format) };
|
|
4671
4693
|
}
|
|
4672
4694
|
var init_recommend2 = __esm({
|
|
4673
4695
|
"src/cli/commands/recommend.ts"() {
|
|
@@ -5002,6 +5024,7 @@ var init_session_types = __esm({
|
|
|
5002
5024
|
"VERIFY",
|
|
5003
5025
|
"FINALIZE",
|
|
5004
5026
|
"COMPACT",
|
|
5027
|
+
"LESSON_CAPTURE",
|
|
5005
5028
|
"ISSUE_SWEEP"
|
|
5006
5029
|
]);
|
|
5007
5030
|
IDLE_STATES = /* @__PURE__ */ new Set([
|
|
@@ -5028,6 +5051,7 @@ var init_session_types = __esm({
|
|
|
5028
5051
|
"COMPACT",
|
|
5029
5052
|
"HANDOVER",
|
|
5030
5053
|
"COMPLETE",
|
|
5054
|
+
"LESSON_CAPTURE",
|
|
5031
5055
|
"ISSUE_SWEEP",
|
|
5032
5056
|
"SESSION_END"
|
|
5033
5057
|
];
|
|
@@ -5594,6 +5618,8 @@ var init_state_machine = __esm({
|
|
|
5594
5618
|
// pass → FINALIZE, fail → IMPLEMENT, retry
|
|
5595
5619
|
FINALIZE: ["COMPLETE"],
|
|
5596
5620
|
COMPLETE: ["PICK_TICKET", "HANDOVER", "ISSUE_SWEEP", "SESSION_END"],
|
|
5621
|
+
LESSON_CAPTURE: ["ISSUE_SWEEP", "HANDOVER", "LESSON_CAPTURE"],
|
|
5622
|
+
// advance → ISSUE_SWEEP, retry self, done → HANDOVER
|
|
5597
5623
|
ISSUE_SWEEP: ["ISSUE_SWEEP", "HANDOVER", "PICK_TICKET"],
|
|
5598
5624
|
// retry (next issue), done → HANDOVER, loop → PICK_TICKET
|
|
5599
5625
|
HANDOVER: ["COMPACT", "SESSION_END", "PICK_TICKET"],
|
|
@@ -5967,6 +5993,18 @@ function findFirstPostComplete(postComplete, ctx) {
|
|
|
5967
5993
|
}
|
|
5968
5994
|
return { kind: "exhausted" };
|
|
5969
5995
|
}
|
|
5996
|
+
function findNextPostComplete(postComplete, currentId, ctx) {
|
|
5997
|
+
const currentIndex = postComplete.indexOf(currentId);
|
|
5998
|
+
const startIndex = currentIndex >= 0 ? currentIndex + 1 : 0;
|
|
5999
|
+
for (let i = startIndex; i < postComplete.length; i++) {
|
|
6000
|
+
const id = postComplete[i];
|
|
6001
|
+
const stage = stages.get(id);
|
|
6002
|
+
if (!stage) return { kind: "unregistered", id };
|
|
6003
|
+
if (stage.skip?.(ctx)) continue;
|
|
6004
|
+
return { kind: "found", stage };
|
|
6005
|
+
}
|
|
6006
|
+
return { kind: "exhausted" };
|
|
6007
|
+
}
|
|
5970
6008
|
var stages;
|
|
5971
6009
|
var init_registry = __esm({
|
|
5972
6010
|
"src/autonomous/stages/registry.ts"() {
|
|
@@ -7403,6 +7441,74 @@ var init_complete = __esm({
|
|
|
7403
7441
|
}
|
|
7404
7442
|
});
|
|
7405
7443
|
|
|
7444
|
+
// src/autonomous/stages/lesson-capture.ts
|
|
7445
|
+
var LessonCaptureStage;
|
|
7446
|
+
var init_lesson_capture = __esm({
|
|
7447
|
+
"src/autonomous/stages/lesson-capture.ts"() {
|
|
7448
|
+
"use strict";
|
|
7449
|
+
init_esm_shims();
|
|
7450
|
+
LessonCaptureStage = class {
|
|
7451
|
+
id = "LESSON_CAPTURE";
|
|
7452
|
+
skip(ctx) {
|
|
7453
|
+
const config = ctx.recipe.stages?.LESSON_CAPTURE;
|
|
7454
|
+
return !config?.enabled;
|
|
7455
|
+
}
|
|
7456
|
+
async enter(ctx) {
|
|
7457
|
+
const planReviews = ctx.state.reviews.plan ?? [];
|
|
7458
|
+
const codeReviews = ctx.state.reviews.code ?? [];
|
|
7459
|
+
const ticketsDone = ctx.state.completedTickets.length;
|
|
7460
|
+
const planFindings = planReviews.reduce((sum, r) => sum + (r.findingCount ?? 0), 0);
|
|
7461
|
+
const planCritical = planReviews.reduce((sum, r) => sum + (r.criticalCount ?? 0), 0);
|
|
7462
|
+
const planMajor = planReviews.reduce((sum, r) => sum + (r.majorCount ?? 0), 0);
|
|
7463
|
+
const codeFindings = codeReviews.reduce((sum, r) => sum + (r.findingCount ?? 0), 0);
|
|
7464
|
+
const codeCritical = codeReviews.reduce((sum, r) => sum + (r.criticalCount ?? 0), 0);
|
|
7465
|
+
const codeMajor = codeReviews.reduce((sum, r) => sum + (r.majorCount ?? 0), 0);
|
|
7466
|
+
const totalFindings = planFindings + codeFindings;
|
|
7467
|
+
if (totalFindings === 0) {
|
|
7468
|
+
ctx.appendEvent("lesson_capture", { result: "no_findings", ticketsDone });
|
|
7469
|
+
return { action: "advance" };
|
|
7470
|
+
}
|
|
7471
|
+
ctx.appendEvent("lesson_capture", {
|
|
7472
|
+
result: "started",
|
|
7473
|
+
ticketsDone,
|
|
7474
|
+
planFindings,
|
|
7475
|
+
codeFindings
|
|
7476
|
+
});
|
|
7477
|
+
return {
|
|
7478
|
+
instruction: [
|
|
7479
|
+
"# Capture Lessons from Review Findings",
|
|
7480
|
+
"",
|
|
7481
|
+
`This session completed ${ticketsDone} ticket(s). Review summary:`,
|
|
7482
|
+
`- **Plan reviews:** ${planReviews.length} round(s), ${planCritical} critical, ${planMajor} major, ${planFindings} total findings`,
|
|
7483
|
+
`- **Code reviews:** ${codeReviews.length} round(s), ${codeCritical} critical, ${codeMajor} major, ${codeFindings} total findings`,
|
|
7484
|
+
"",
|
|
7485
|
+
"Review these findings for recurring patterns worth capturing as lessons:",
|
|
7486
|
+
"",
|
|
7487
|
+
"1. Call `claudestory_lesson_list` to see existing lessons",
|
|
7488
|
+
"2. For each pattern that recurred or was critical/major:",
|
|
7489
|
+
" - If it matches an existing lesson \u2192 call `claudestory_lesson_reinforce`",
|
|
7490
|
+
' - If it\'s a new pattern \u2192 call `claudestory_lesson_create` with `source: "review"`',
|
|
7491
|
+
"3. Skip patterns that are one-off or already well-covered",
|
|
7492
|
+
`4. Call \`claudestory_autonomous_guide\` with completedAction: "lessons_captured"`,
|
|
7493
|
+
"",
|
|
7494
|
+
"```json",
|
|
7495
|
+
`{ "sessionId": "${ctx.state.sessionId}", "action": "report", "report": { "completedAction": "lessons_captured" } }`,
|
|
7496
|
+
"```"
|
|
7497
|
+
].join("\n"),
|
|
7498
|
+
reminders: [
|
|
7499
|
+
"Check existing lessons first \u2014 reinforce before creating duplicates.",
|
|
7500
|
+
"Only capture patterns worth remembering across sessions."
|
|
7501
|
+
]
|
|
7502
|
+
};
|
|
7503
|
+
}
|
|
7504
|
+
async report(ctx, _report) {
|
|
7505
|
+
ctx.appendEvent("lesson_capture", { result: "completed" });
|
|
7506
|
+
return { action: "advance" };
|
|
7507
|
+
}
|
|
7508
|
+
};
|
|
7509
|
+
}
|
|
7510
|
+
});
|
|
7511
|
+
|
|
7406
7512
|
// src/autonomous/stages/issue-sweep.ts
|
|
7407
7513
|
var IssueSweepStage;
|
|
7408
7514
|
var init_issue_sweep = __esm({
|
|
@@ -7611,6 +7717,7 @@ var init_stages = __esm({
|
|
|
7611
7717
|
init_verify();
|
|
7612
7718
|
init_finalize();
|
|
7613
7719
|
init_complete();
|
|
7720
|
+
init_lesson_capture();
|
|
7614
7721
|
init_issue_sweep();
|
|
7615
7722
|
init_handover2();
|
|
7616
7723
|
registerStage(new PickTicketStage());
|
|
@@ -7623,6 +7730,7 @@ var init_stages = __esm({
|
|
|
7623
7730
|
registerStage(new VerifyStage());
|
|
7624
7731
|
registerStage(new FinalizeStage());
|
|
7625
7732
|
registerStage(new CompleteStage());
|
|
7733
|
+
registerStage(new LessonCaptureStage());
|
|
7626
7734
|
registerStage(new IssueSweepStage());
|
|
7627
7735
|
registerStage(new HandoverStage());
|
|
7628
7736
|
}
|
|
@@ -8238,7 +8346,8 @@ async function processAdvance(ctx, currentStage, advance, depth = 0) {
|
|
|
8238
8346
|
}
|
|
8239
8347
|
if (next.kind === "exhausted") {
|
|
8240
8348
|
const postComplete = ctx.state.resolvedPostComplete ?? ctx.recipe.postComplete;
|
|
8241
|
-
const
|
|
8349
|
+
const isInPostComplete = postComplete.includes(currentStage.id);
|
|
8350
|
+
const post = isInPostComplete ? findNextPostComplete(postComplete, currentStage.id, ctx) : findFirstPostComplete(postComplete, ctx);
|
|
8242
8351
|
if (post.kind === "found") {
|
|
8243
8352
|
assertTransition(currentStage.id, post.stage.id);
|
|
8244
8353
|
ctx.writeState({ state: post.stage.id, previousState: currentStage.id });
|
|
@@ -8384,6 +8493,7 @@ async function handleResume(root, args) {
|
|
|
8384
8493
|
// T-128: tests invalidated by HEAD change
|
|
8385
8494
|
CODE_REVIEW: { state: "PLAN", resetPlan: true, resetCode: true },
|
|
8386
8495
|
FINALIZE: { state: "IMPLEMENT", resetPlan: false, resetCode: true },
|
|
8496
|
+
LESSON_CAPTURE: { state: "PICK_TICKET", resetPlan: false, resetCode: false },
|
|
8387
8497
|
ISSUE_SWEEP: { state: "PICK_TICKET", resetPlan: false, resetCode: false }
|
|
8388
8498
|
// T-128: post-complete, restart sweep
|
|
8389
8499
|
};
|
|
@@ -10129,7 +10239,7 @@ var init_mcp = __esm({
|
|
|
10129
10239
|
init_init();
|
|
10130
10240
|
ENV_VAR2 = "CLAUDESTORY_PROJECT_ROOT";
|
|
10131
10241
|
CONFIG_PATH2 = ".story/config.json";
|
|
10132
|
-
version = "0.1.
|
|
10242
|
+
version = "0.1.29";
|
|
10133
10243
|
main().catch((err) => {
|
|
10134
10244
|
process.stderr.write(`Fatal: ${err instanceof Error ? err.message : String(err)}
|
|
10135
10245
|
`);
|
|
@@ -13407,7 +13517,7 @@ async function runCli() {
|
|
|
13407
13517
|
registerConfigCommand: registerConfigCommand2,
|
|
13408
13518
|
registerSessionCommand: registerSessionCommand2
|
|
13409
13519
|
} = await Promise.resolve().then(() => (init_register(), register_exports));
|
|
13410
|
-
const version2 = "0.1.
|
|
13520
|
+
const version2 = "0.1.29";
|
|
13411
13521
|
class HandledError extends Error {
|
|
13412
13522
|
constructor() {
|
|
13413
13523
|
super("HANDLED_ERROR");
|
package/dist/index.d.ts
CHANGED
|
@@ -561,6 +561,9 @@ declare class ProjectState {
|
|
|
561
561
|
*/
|
|
562
562
|
isBlocked(ticket: Ticket): boolean;
|
|
563
563
|
get blockedCount(): number;
|
|
564
|
+
/** True when the project has been initialized but not yet populated with tickets/issues/handovers. */
|
|
565
|
+
get isEmptyScaffold(): boolean;
|
|
566
|
+
private get isDefaultScaffoldPhases();
|
|
564
567
|
ticketByID(id: string): Ticket | undefined;
|
|
565
568
|
issueByID(id: string): Issue | undefined;
|
|
566
569
|
noteByID(id: string): Note | undefined;
|
|
@@ -1722,6 +1725,8 @@ declare function buildRecap(currentState: ProjectState, snapshotInfo: {
|
|
|
1722
1725
|
filename: string;
|
|
1723
1726
|
} | null): RecapResult;
|
|
1724
1727
|
|
|
1728
|
+
/** SKILL PROTOCOL: SKILL.md Step 2b matches this literal string. Do not change without updating SKILL.md. */
|
|
1729
|
+
declare const EMPTY_SCAFFOLD_HEADING = "## Getting Started";
|
|
1725
1730
|
declare const ExitCode: {
|
|
1726
1731
|
readonly OK: 0;
|
|
1727
1732
|
readonly USER_ERROR: 1;
|
|
@@ -1792,6 +1797,6 @@ declare function formatSnapshotResult(result: {
|
|
|
1792
1797
|
}, format: OutputFormat): string;
|
|
1793
1798
|
declare function formatRecap(recap: RecapResult, state: ProjectState, format: OutputFormat): string;
|
|
1794
1799
|
declare function formatExport(state: ProjectState, mode: "all" | "phase", phaseId: string | null, format: OutputFormat): string;
|
|
1795
|
-
declare function formatRecommendations(result: RecommendResult, format: OutputFormat): string;
|
|
1800
|
+
declare function formatRecommendations(result: RecommendResult, state: ProjectState, format: OutputFormat): string;
|
|
1796
1801
|
|
|
1797
|
-
export { type Blocker, BlockerSchema, CURRENT_SCHEMA_VERSION, type Config, ConfigSchema, DATE_REGEX, DateSchema, ERROR_CODES, type ErrorCode, type ErrorEnvelope, ExitCode, type ExitCodeValue, type Features, FeaturesSchema, INTEGRITY_WARNING_TYPES, ISSUE_ID_REGEX, ISSUE_SEVERITIES, ISSUE_STATUSES, type InitOptions, type InitResult, type Issue, IssueIdSchema, IssueSchema, type IssueSeverity, type IssueStatus, type LoadOptions, type LoadResult, type LoadWarning, type LoadWarningType, NOTE_ID_REGEX, NOTE_STATUSES, type NextTicketAllBlocked, type NextTicketAllComplete, type NextTicketEmpty, type NextTicketOutcome, type NextTicketResult, type Note, NoteIdSchema, NoteSchema, type NoteStatus, OUTPUT_FORMATS, type OutputFormat, type PartialEnvelope, type Phase, PhaseSchema, type PhaseStatus, type PhaseWithStatus, ProjectLoaderError, ProjectState, type RecapResult, type RecommendCategory, type RecommendItemKind, type RecommendResult, type Recommendation, type Roadmap, RoadmapSchema, type SnapshotDiff, type SnapshotV1, SnapshotV1Schema, type SuccessEnvelope, TICKET_ID_REGEX, TICKET_STATUSES, TICKET_TYPES, type Ticket, TicketIdSchema, TicketSchema, type TicketStatus, type TicketType, type UmbrellaProgress, type UnblockImpact, type ValidationFinding, type ValidationLevel, type ValidationResult, type WithProjectLockOptions, atomicWrite, blockedTickets, buildRecap, currentPhase, deleteIssue, deleteNote, deleteTicket, descendantLeaves, diffStates, discoverProjectRoot, errorEnvelope, escapeMarkdownInline, extractHandoverDate, fencedBlock, formatBlockedTickets, formatBlockerList, formatError, formatExport, formatHandoverContent, formatHandoverCreateResult, formatHandoverList, formatInitResult, formatIssue, formatIssueList, formatNextTicketOutcome, formatPhaseList, formatPhaseTickets, formatRecap, formatRecommendations, formatSnapshotResult, formatStatus, formatTicket, formatTicketList, formatValidation, guardPath, initProject, isBlockerCleared, listHandovers, loadLatestSnapshot, loadProject, mergeValidation, nextIssueID, nextNoteID, nextOrder, nextTicket, nextTicketID, nextTickets, partialEnvelope, phasesWithStatus, readHandover, recommend, runTransaction, runTransactionUnlocked, saveSnapshot, serializeJSON, sortKeysDeep, successEnvelope, ticketsUnblockedBy, umbrellaProgress, validateProject, withProjectLock, writeConfig, writeIssue, writeIssueUnlocked, writeNote, writeNoteUnlocked, writeRoadmap, writeRoadmapUnlocked, writeTicket, writeTicketUnlocked };
|
|
1802
|
+
export { type Blocker, BlockerSchema, CURRENT_SCHEMA_VERSION, type Config, ConfigSchema, DATE_REGEX, DateSchema, EMPTY_SCAFFOLD_HEADING, ERROR_CODES, type ErrorCode, type ErrorEnvelope, ExitCode, type ExitCodeValue, type Features, FeaturesSchema, INTEGRITY_WARNING_TYPES, ISSUE_ID_REGEX, ISSUE_SEVERITIES, ISSUE_STATUSES, type InitOptions, type InitResult, type Issue, IssueIdSchema, IssueSchema, type IssueSeverity, type IssueStatus, type LoadOptions, type LoadResult, type LoadWarning, type LoadWarningType, NOTE_ID_REGEX, NOTE_STATUSES, type NextTicketAllBlocked, type NextTicketAllComplete, type NextTicketEmpty, type NextTicketOutcome, type NextTicketResult, type Note, NoteIdSchema, NoteSchema, type NoteStatus, OUTPUT_FORMATS, type OutputFormat, type PartialEnvelope, type Phase, PhaseSchema, type PhaseStatus, type PhaseWithStatus, ProjectLoaderError, ProjectState, type RecapResult, type RecommendCategory, type RecommendItemKind, type RecommendResult, type Recommendation, type Roadmap, RoadmapSchema, type SnapshotDiff, type SnapshotV1, SnapshotV1Schema, type SuccessEnvelope, TICKET_ID_REGEX, TICKET_STATUSES, TICKET_TYPES, type Ticket, TicketIdSchema, TicketSchema, type TicketStatus, type TicketType, type UmbrellaProgress, type UnblockImpact, type ValidationFinding, type ValidationLevel, type ValidationResult, type WithProjectLockOptions, atomicWrite, blockedTickets, buildRecap, currentPhase, deleteIssue, deleteNote, deleteTicket, descendantLeaves, diffStates, discoverProjectRoot, errorEnvelope, escapeMarkdownInline, extractHandoverDate, fencedBlock, formatBlockedTickets, formatBlockerList, formatError, formatExport, formatHandoverContent, formatHandoverCreateResult, formatHandoverList, formatInitResult, formatIssue, formatIssueList, formatNextTicketOutcome, formatPhaseList, formatPhaseTickets, formatRecap, formatRecommendations, formatSnapshotResult, formatStatus, formatTicket, formatTicketList, formatValidation, guardPath, initProject, isBlockerCleared, listHandovers, loadLatestSnapshot, loadProject, mergeValidation, nextIssueID, nextNoteID, nextOrder, nextTicket, nextTicketID, nextTickets, partialEnvelope, phasesWithStatus, readHandover, recommend, runTransaction, runTransactionUnlocked, saveSnapshot, serializeJSON, sortKeysDeep, successEnvelope, ticketsUnblockedBy, umbrellaProgress, validateProject, withProjectLock, writeConfig, writeIssue, writeIssueUnlocked, writeNote, writeNoteUnlocked, writeRoadmap, writeRoadmapUnlocked, writeTicket, writeTicketUnlocked };
|
package/dist/index.js
CHANGED
|
@@ -326,6 +326,15 @@ var ProjectState = class _ProjectState {
|
|
|
326
326
|
get blockedCount() {
|
|
327
327
|
return this.leafTickets.filter((t) => t.status !== "complete" && this.isBlocked(t)).length;
|
|
328
328
|
}
|
|
329
|
+
/** True when the project has been initialized but not yet populated with tickets/issues/handovers. */
|
|
330
|
+
get isEmptyScaffold() {
|
|
331
|
+
return this.tickets.length === 0 && this.issues.length === 0 && this.handoverFilenames.length === 0 && this.isDefaultScaffoldPhases;
|
|
332
|
+
}
|
|
333
|
+
get isDefaultScaffoldPhases() {
|
|
334
|
+
const { phases } = this.roadmap;
|
|
335
|
+
if (phases.length === 0) return true;
|
|
336
|
+
return phases.length === 1 && phases[0].id === "p0";
|
|
337
|
+
}
|
|
329
338
|
ticketByID(id) {
|
|
330
339
|
return this.ticketsByID.get(id);
|
|
331
340
|
}
|
|
@@ -2289,6 +2298,7 @@ async function pruneSnapshots(dir) {
|
|
|
2289
2298
|
}
|
|
2290
2299
|
|
|
2291
2300
|
// src/core/output-formatter.ts
|
|
2301
|
+
var EMPTY_SCAFFOLD_HEADING = "## Getting Started";
|
|
2292
2302
|
var ExitCode = {
|
|
2293
2303
|
OK: 0,
|
|
2294
2304
|
USER_ERROR: 1,
|
|
@@ -2343,6 +2353,7 @@ function formatStatus(state, format) {
|
|
|
2343
2353
|
activeLessons: state.activeLessonCount,
|
|
2344
2354
|
deprecatedLessons: state.deprecatedLessonCount,
|
|
2345
2355
|
handovers: state.handoverFilenames.length,
|
|
2356
|
+
isEmptyScaffold: state.isEmptyScaffold,
|
|
2346
2357
|
phases: phases.map((p) => ({
|
|
2347
2358
|
id: p.phase.id,
|
|
2348
2359
|
name: p.phase.name,
|
|
@@ -2370,6 +2381,13 @@ function formatStatus(state, format) {
|
|
|
2370
2381
|
const summary = p.phase.summary ?? truncate(p.phase.description, 80);
|
|
2371
2382
|
lines.push(`${indicator} **${escapeMarkdownInline(p.phase.name)}** (${p.leafCount} tickets) \u2014 ${escapeMarkdownInline(summary)}`);
|
|
2372
2383
|
}
|
|
2384
|
+
if (state.isEmptyScaffold) {
|
|
2385
|
+
lines.push("");
|
|
2386
|
+
lines.push(EMPTY_SCAFFOLD_HEADING);
|
|
2387
|
+
lines.push("");
|
|
2388
|
+
lines.push("This project has been initialized but has no tickets, issues, or handovers yet.");
|
|
2389
|
+
lines.push("Run the /story setup flow to analyze your project and create an initial roadmap.");
|
|
2390
|
+
}
|
|
2373
2391
|
return lines.join("\n");
|
|
2374
2392
|
}
|
|
2375
2393
|
function formatPhaseList(state, format) {
|
|
@@ -2967,11 +2985,14 @@ function formatTicketOneLiner(t, state) {
|
|
|
2967
2985
|
const blocked = state.isBlocked(t) ? " [BLOCKED]" : "";
|
|
2968
2986
|
return `${status} ${t.id}: ${escapeMarkdownInline(t.title)}${blocked}`;
|
|
2969
2987
|
}
|
|
2970
|
-
function formatRecommendations(result, format) {
|
|
2988
|
+
function formatRecommendations(result, state, format) {
|
|
2971
2989
|
if (format === "json") {
|
|
2972
|
-
return JSON.stringify(successEnvelope(result), null, 2);
|
|
2990
|
+
return JSON.stringify(successEnvelope({ ...result, isEmptyScaffold: state.isEmptyScaffold }), null, 2);
|
|
2973
2991
|
}
|
|
2974
2992
|
if (result.recommendations.length === 0) {
|
|
2993
|
+
if (state.isEmptyScaffold) {
|
|
2994
|
+
return "No recommendations yet \u2014 this project needs tickets and phases. Run the /story setup flow to get started.";
|
|
2995
|
+
}
|
|
2975
2996
|
return "No recommendations \u2014 all work is complete or blocked.";
|
|
2976
2997
|
}
|
|
2977
2998
|
const lines = ["# Recommendations", ""];
|
|
@@ -2996,6 +3017,7 @@ export {
|
|
|
2996
3017
|
ConfigSchema,
|
|
2997
3018
|
DATE_REGEX,
|
|
2998
3019
|
DateSchema,
|
|
3020
|
+
EMPTY_SCAFFOLD_HEADING,
|
|
2999
3021
|
ERROR_CODES,
|
|
3000
3022
|
ExitCode,
|
|
3001
3023
|
FeaturesSchema,
|
package/dist/mcp.js
CHANGED
|
@@ -433,6 +433,15 @@ var init_project_state = __esm({
|
|
|
433
433
|
get blockedCount() {
|
|
434
434
|
return this.leafTickets.filter((t) => t.status !== "complete" && this.isBlocked(t)).length;
|
|
435
435
|
}
|
|
436
|
+
/** True when the project has been initialized but not yet populated with tickets/issues/handovers. */
|
|
437
|
+
get isEmptyScaffold() {
|
|
438
|
+
return this.tickets.length === 0 && this.issues.length === 0 && this.handoverFilenames.length === 0 && this.isDefaultScaffoldPhases;
|
|
439
|
+
}
|
|
440
|
+
get isDefaultScaffoldPhases() {
|
|
441
|
+
const { phases } = this.roadmap;
|
|
442
|
+
if (phases.length === 0) return true;
|
|
443
|
+
return phases.length === 1 && phases[0].id === "p0";
|
|
444
|
+
}
|
|
436
445
|
ticketByID(id) {
|
|
437
446
|
return this.ticketsByID.get(id);
|
|
438
447
|
}
|
|
@@ -1563,6 +1572,7 @@ function formatStatus(state, format) {
|
|
|
1563
1572
|
activeLessons: state.activeLessonCount,
|
|
1564
1573
|
deprecatedLessons: state.deprecatedLessonCount,
|
|
1565
1574
|
handovers: state.handoverFilenames.length,
|
|
1575
|
+
isEmptyScaffold: state.isEmptyScaffold,
|
|
1566
1576
|
phases: phases.map((p) => ({
|
|
1567
1577
|
id: p.phase.id,
|
|
1568
1578
|
name: p.phase.name,
|
|
@@ -1590,6 +1600,13 @@ function formatStatus(state, format) {
|
|
|
1590
1600
|
const summary = p.phase.summary ?? truncate(p.phase.description, 80);
|
|
1591
1601
|
lines.push(`${indicator} **${escapeMarkdownInline(p.phase.name)}** (${p.leafCount} tickets) \u2014 ${escapeMarkdownInline(summary)}`);
|
|
1592
1602
|
}
|
|
1603
|
+
if (state.isEmptyScaffold) {
|
|
1604
|
+
lines.push("");
|
|
1605
|
+
lines.push(EMPTY_SCAFFOLD_HEADING);
|
|
1606
|
+
lines.push("");
|
|
1607
|
+
lines.push("This project has been initialized but has no tickets, issues, or handovers yet.");
|
|
1608
|
+
lines.push("Run the /story setup flow to analyze your project and create an initial roadmap.");
|
|
1609
|
+
}
|
|
1593
1610
|
return lines.join("\n");
|
|
1594
1611
|
}
|
|
1595
1612
|
function formatPhaseList(state, format) {
|
|
@@ -2356,11 +2373,14 @@ function formatTicketOneLiner(t, state) {
|
|
|
2356
2373
|
const blocked = state.isBlocked(t) ? " [BLOCKED]" : "";
|
|
2357
2374
|
return `${status} ${t.id}: ${escapeMarkdownInline(t.title)}${blocked}`;
|
|
2358
2375
|
}
|
|
2359
|
-
function formatRecommendations(result, format) {
|
|
2376
|
+
function formatRecommendations(result, state, format) {
|
|
2360
2377
|
if (format === "json") {
|
|
2361
|
-
return JSON.stringify(successEnvelope(result), null, 2);
|
|
2378
|
+
return JSON.stringify(successEnvelope({ ...result, isEmptyScaffold: state.isEmptyScaffold }), null, 2);
|
|
2362
2379
|
}
|
|
2363
2380
|
if (result.recommendations.length === 0) {
|
|
2381
|
+
if (state.isEmptyScaffold) {
|
|
2382
|
+
return "No recommendations yet \u2014 this project needs tickets and phases. Run the /story setup flow to get started.";
|
|
2383
|
+
}
|
|
2364
2384
|
return "No recommendations \u2014 all work is complete or blocked.";
|
|
2365
2385
|
}
|
|
2366
2386
|
const lines = ["# Recommendations", ""];
|
|
@@ -2379,12 +2399,13 @@ function formatRecommendations(result, format) {
|
|
|
2379
2399
|
}
|
|
2380
2400
|
return lines.join("\n");
|
|
2381
2401
|
}
|
|
2382
|
-
var ExitCode;
|
|
2402
|
+
var EMPTY_SCAFFOLD_HEADING, ExitCode;
|
|
2383
2403
|
var init_output_formatter = __esm({
|
|
2384
2404
|
"src/core/output-formatter.ts"() {
|
|
2385
2405
|
"use strict";
|
|
2386
2406
|
init_esm_shims();
|
|
2387
2407
|
init_queries();
|
|
2408
|
+
EMPTY_SCAFFOLD_HEADING = "## Getting Started";
|
|
2388
2409
|
ExitCode = {
|
|
2389
2410
|
OK: 0,
|
|
2390
2411
|
USER_ERROR: 1,
|
|
@@ -3472,6 +3493,7 @@ var init_session_types = __esm({
|
|
|
3472
3493
|
"COMPACT",
|
|
3473
3494
|
"HANDOVER",
|
|
3474
3495
|
"COMPLETE",
|
|
3496
|
+
"LESSON_CAPTURE",
|
|
3475
3497
|
"ISSUE_SWEEP",
|
|
3476
3498
|
"SESSION_END"
|
|
3477
3499
|
];
|
|
@@ -4883,7 +4905,7 @@ function sortByPhaseAndOrder(tickets, phaseIndex) {
|
|
|
4883
4905
|
init_output_formatter();
|
|
4884
4906
|
function handleRecommend(ctx, count) {
|
|
4885
4907
|
const result = recommend(ctx.state, count);
|
|
4886
|
-
return { output: formatRecommendations(result, ctx.format) };
|
|
4908
|
+
return { output: formatRecommendations(result, ctx.state, ctx.format) };
|
|
4887
4909
|
}
|
|
4888
4910
|
|
|
4889
4911
|
// src/cli/commands/snapshot.ts
|
|
@@ -5200,6 +5222,8 @@ var TRANSITIONS = {
|
|
|
5200
5222
|
// pass → FINALIZE, fail → IMPLEMENT, retry
|
|
5201
5223
|
FINALIZE: ["COMPLETE"],
|
|
5202
5224
|
COMPLETE: ["PICK_TICKET", "HANDOVER", "ISSUE_SWEEP", "SESSION_END"],
|
|
5225
|
+
LESSON_CAPTURE: ["ISSUE_SWEEP", "HANDOVER", "LESSON_CAPTURE"],
|
|
5226
|
+
// advance → ISSUE_SWEEP, retry self, done → HANDOVER
|
|
5203
5227
|
ISSUE_SWEEP: ["ISSUE_SWEEP", "HANDOVER", "PICK_TICKET"],
|
|
5204
5228
|
// retry (next issue), done → HANDOVER, loop → PICK_TICKET
|
|
5205
5229
|
HANDOVER: ["COMPACT", "SESSION_END", "PICK_TICKET"],
|
|
@@ -5559,6 +5583,18 @@ function findFirstPostComplete(postComplete, ctx) {
|
|
|
5559
5583
|
}
|
|
5560
5584
|
return { kind: "exhausted" };
|
|
5561
5585
|
}
|
|
5586
|
+
function findNextPostComplete(postComplete, currentId, ctx) {
|
|
5587
|
+
const currentIndex = postComplete.indexOf(currentId);
|
|
5588
|
+
const startIndex = currentIndex >= 0 ? currentIndex + 1 : 0;
|
|
5589
|
+
for (let i = startIndex; i < postComplete.length; i++) {
|
|
5590
|
+
const id = postComplete[i];
|
|
5591
|
+
const stage = stages.get(id);
|
|
5592
|
+
if (!stage) return { kind: "unregistered", id };
|
|
5593
|
+
if (stage.skip?.(ctx)) continue;
|
|
5594
|
+
return { kind: "found", stage };
|
|
5595
|
+
}
|
|
5596
|
+
return { kind: "exhausted" };
|
|
5597
|
+
}
|
|
5562
5598
|
|
|
5563
5599
|
// src/autonomous/stages/types.ts
|
|
5564
5600
|
init_esm_shims();
|
|
@@ -6915,6 +6951,68 @@ var CompleteStage = class {
|
|
|
6915
6951
|
}
|
|
6916
6952
|
};
|
|
6917
6953
|
|
|
6954
|
+
// src/autonomous/stages/lesson-capture.ts
|
|
6955
|
+
init_esm_shims();
|
|
6956
|
+
var LessonCaptureStage = class {
|
|
6957
|
+
id = "LESSON_CAPTURE";
|
|
6958
|
+
skip(ctx) {
|
|
6959
|
+
const config = ctx.recipe.stages?.LESSON_CAPTURE;
|
|
6960
|
+
return !config?.enabled;
|
|
6961
|
+
}
|
|
6962
|
+
async enter(ctx) {
|
|
6963
|
+
const planReviews = ctx.state.reviews.plan ?? [];
|
|
6964
|
+
const codeReviews = ctx.state.reviews.code ?? [];
|
|
6965
|
+
const ticketsDone = ctx.state.completedTickets.length;
|
|
6966
|
+
const planFindings = planReviews.reduce((sum, r) => sum + (r.findingCount ?? 0), 0);
|
|
6967
|
+
const planCritical = planReviews.reduce((sum, r) => sum + (r.criticalCount ?? 0), 0);
|
|
6968
|
+
const planMajor = planReviews.reduce((sum, r) => sum + (r.majorCount ?? 0), 0);
|
|
6969
|
+
const codeFindings = codeReviews.reduce((sum, r) => sum + (r.findingCount ?? 0), 0);
|
|
6970
|
+
const codeCritical = codeReviews.reduce((sum, r) => sum + (r.criticalCount ?? 0), 0);
|
|
6971
|
+
const codeMajor = codeReviews.reduce((sum, r) => sum + (r.majorCount ?? 0), 0);
|
|
6972
|
+
const totalFindings = planFindings + codeFindings;
|
|
6973
|
+
if (totalFindings === 0) {
|
|
6974
|
+
ctx.appendEvent("lesson_capture", { result: "no_findings", ticketsDone });
|
|
6975
|
+
return { action: "advance" };
|
|
6976
|
+
}
|
|
6977
|
+
ctx.appendEvent("lesson_capture", {
|
|
6978
|
+
result: "started",
|
|
6979
|
+
ticketsDone,
|
|
6980
|
+
planFindings,
|
|
6981
|
+
codeFindings
|
|
6982
|
+
});
|
|
6983
|
+
return {
|
|
6984
|
+
instruction: [
|
|
6985
|
+
"# Capture Lessons from Review Findings",
|
|
6986
|
+
"",
|
|
6987
|
+
`This session completed ${ticketsDone} ticket(s). Review summary:`,
|
|
6988
|
+
`- **Plan reviews:** ${planReviews.length} round(s), ${planCritical} critical, ${planMajor} major, ${planFindings} total findings`,
|
|
6989
|
+
`- **Code reviews:** ${codeReviews.length} round(s), ${codeCritical} critical, ${codeMajor} major, ${codeFindings} total findings`,
|
|
6990
|
+
"",
|
|
6991
|
+
"Review these findings for recurring patterns worth capturing as lessons:",
|
|
6992
|
+
"",
|
|
6993
|
+
"1. Call `claudestory_lesson_list` to see existing lessons",
|
|
6994
|
+
"2. For each pattern that recurred or was critical/major:",
|
|
6995
|
+
" - If it matches an existing lesson \u2192 call `claudestory_lesson_reinforce`",
|
|
6996
|
+
' - If it\'s a new pattern \u2192 call `claudestory_lesson_create` with `source: "review"`',
|
|
6997
|
+
"3. Skip patterns that are one-off or already well-covered",
|
|
6998
|
+
`4. Call \`claudestory_autonomous_guide\` with completedAction: "lessons_captured"`,
|
|
6999
|
+
"",
|
|
7000
|
+
"```json",
|
|
7001
|
+
`{ "sessionId": "${ctx.state.sessionId}", "action": "report", "report": { "completedAction": "lessons_captured" } }`,
|
|
7002
|
+
"```"
|
|
7003
|
+
].join("\n"),
|
|
7004
|
+
reminders: [
|
|
7005
|
+
"Check existing lessons first \u2014 reinforce before creating duplicates.",
|
|
7006
|
+
"Only capture patterns worth remembering across sessions."
|
|
7007
|
+
]
|
|
7008
|
+
};
|
|
7009
|
+
}
|
|
7010
|
+
async report(ctx, _report) {
|
|
7011
|
+
ctx.appendEvent("lesson_capture", { result: "completed" });
|
|
7012
|
+
return { action: "advance" };
|
|
7013
|
+
}
|
|
7014
|
+
};
|
|
7015
|
+
|
|
6918
7016
|
// src/autonomous/stages/issue-sweep.ts
|
|
6919
7017
|
init_esm_shims();
|
|
6920
7018
|
var IssueSweepStage = class {
|
|
@@ -7105,6 +7203,7 @@ registerStage(new CodeReviewStage());
|
|
|
7105
7203
|
registerStage(new VerifyStage());
|
|
7106
7204
|
registerStage(new FinalizeStage());
|
|
7107
7205
|
registerStage(new CompleteStage());
|
|
7206
|
+
registerStage(new LessonCaptureStage());
|
|
7108
7207
|
registerStage(new IssueSweepStage());
|
|
7109
7208
|
registerStage(new HandoverStage());
|
|
7110
7209
|
|
|
@@ -7728,7 +7827,8 @@ async function processAdvance(ctx, currentStage, advance, depth = 0) {
|
|
|
7728
7827
|
}
|
|
7729
7828
|
if (next.kind === "exhausted") {
|
|
7730
7829
|
const postComplete = ctx.state.resolvedPostComplete ?? ctx.recipe.postComplete;
|
|
7731
|
-
const
|
|
7830
|
+
const isInPostComplete = postComplete.includes(currentStage.id);
|
|
7831
|
+
const post = isInPostComplete ? findNextPostComplete(postComplete, currentStage.id, ctx) : findFirstPostComplete(postComplete, ctx);
|
|
7732
7832
|
if (post.kind === "found") {
|
|
7733
7833
|
assertTransition(currentStage.id, post.stage.id);
|
|
7734
7834
|
ctx.writeState({ state: post.stage.id, previousState: currentStage.id });
|
|
@@ -7874,6 +7974,7 @@ async function handleResume(root, args) {
|
|
|
7874
7974
|
// T-128: tests invalidated by HEAD change
|
|
7875
7975
|
CODE_REVIEW: { state: "PLAN", resetPlan: true, resetCode: true },
|
|
7876
7976
|
FINALIZE: { state: "IMPLEMENT", resetPlan: false, resetCode: true },
|
|
7977
|
+
LESSON_CAPTURE: { state: "PICK_TICKET", resetPlan: false, resetCode: false },
|
|
7877
7978
|
ISSUE_SWEEP: { state: "PICK_TICKET", resetPlan: false, resetCode: false }
|
|
7878
7979
|
// T-128: post-complete, restart sweep
|
|
7879
7980
|
};
|
|
@@ -9294,7 +9395,7 @@ async function ensureGitignoreEntries(gitignorePath, entries) {
|
|
|
9294
9395
|
// src/mcp/index.ts
|
|
9295
9396
|
var ENV_VAR2 = "CLAUDESTORY_PROJECT_ROOT";
|
|
9296
9397
|
var CONFIG_PATH2 = ".story/config.json";
|
|
9297
|
-
var version = "0.1.
|
|
9398
|
+
var version = "0.1.29";
|
|
9298
9399
|
function tryDiscoverRoot() {
|
|
9299
9400
|
const envRoot = process.env[ENV_VAR2];
|
|
9300
9401
|
if (envRoot) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@anthropologies/claudestory",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.29",
|
|
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": [
|
package/src/skill/SKILL.md
CHANGED
|
@@ -199,6 +199,14 @@ Call these in order:
|
|
|
199
199
|
5. **Lessons learned** — call `claudestory_lesson_digest` MCP tool
|
|
200
200
|
6. **Recent commits** — run `git log --oneline -10`
|
|
201
201
|
|
|
202
|
+
## Step 2b: Empty Scaffold Check
|
|
203
|
+
|
|
204
|
+
After `claudestory_status` returns, check in order:
|
|
205
|
+
|
|
206
|
+
1. **Integrity guard** — if the response starts with "Warning:" and contains "item(s) skipped due to data integrity issues", this is NOT an empty scaffold. Tell the user to run `claudestory validate`. Continue Step 2/3 normally.
|
|
207
|
+
2. **Scaffold detection** — check BOTH: output contains "## Getting Started" AND shows `Tickets: 0/0 complete` + `Handovers: 0`. If met AND the project has code indicators (git history, package manifest, source files), route to the AI-Assisted Setup Flow (section 1b above) instead of Step 3. Skip remaining Step 2 calls and Step 3.
|
|
208
|
+
3. **Empty without code** — if scaffold detected but no code indicators (truly empty directory), continue to Step 3 which will show: "Your project is set up but has no tickets yet. Would you like me to help you create your first phase and tickets?"
|
|
209
|
+
|
|
202
210
|
## Step 3: Present Summary
|
|
203
211
|
|
|
204
212
|
After loading context, present a concise summary:
|