@anthropologies/claudestory 0.1.11 → 0.1.13
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 +1872 -143
- package/dist/index.d.ts +42 -34
- package/dist/index.js +22 -4
- package/dist/mcp.js +2865 -1203
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -261,7 +261,8 @@ var init_config = __esm({
|
|
|
261
261
|
project: z6.string().min(1),
|
|
262
262
|
type: z6.string(),
|
|
263
263
|
language: z6.string(),
|
|
264
|
-
features: FeaturesSchema
|
|
264
|
+
features: FeaturesSchema,
|
|
265
|
+
recipe: z6.string().optional()
|
|
265
266
|
}).passthrough();
|
|
266
267
|
}
|
|
267
268
|
});
|
|
@@ -2772,9 +2773,9 @@ async function handleHandoverCreate(content, slugRaw, format, root) {
|
|
|
2772
2773
|
const datePrefix = `${date}-`;
|
|
2773
2774
|
const seqRegex = new RegExp(`^${date}-(\\d{2})-`);
|
|
2774
2775
|
let maxSeq = 0;
|
|
2775
|
-
const { readdirSync } = await import("fs");
|
|
2776
|
+
const { readdirSync: readdirSync2 } = await import("fs");
|
|
2776
2777
|
try {
|
|
2777
|
-
for (const f of
|
|
2778
|
+
for (const f of readdirSync2(handoversDir)) {
|
|
2778
2779
|
const m = f.match(seqRegex);
|
|
2779
2780
|
if (m) {
|
|
2780
2781
|
const n = parseInt(m[1], 10);
|
|
@@ -3363,6 +3364,14 @@ var init_issue2 = __esm({
|
|
|
3363
3364
|
});
|
|
3364
3365
|
|
|
3365
3366
|
// src/core/snapshot.ts
|
|
3367
|
+
var snapshot_exports = {};
|
|
3368
|
+
__export(snapshot_exports, {
|
|
3369
|
+
SnapshotV1Schema: () => SnapshotV1Schema,
|
|
3370
|
+
buildRecap: () => buildRecap,
|
|
3371
|
+
diffStates: () => diffStates,
|
|
3372
|
+
loadLatestSnapshot: () => loadLatestSnapshot,
|
|
3373
|
+
saveSnapshot: () => saveSnapshot
|
|
3374
|
+
});
|
|
3366
3375
|
import { readdir as readdir3, readFile as readFile3, mkdir as mkdir3, unlink as unlink2 } from "fs/promises";
|
|
3367
3376
|
import { existsSync as existsSync5 } from "fs";
|
|
3368
3377
|
import { join as join5, resolve as resolve5 } from "path";
|
|
@@ -4341,8 +4350,1517 @@ var init_selftest = __esm({
|
|
|
4341
4350
|
}
|
|
4342
4351
|
});
|
|
4343
4352
|
|
|
4353
|
+
// src/autonomous/session-types.ts
|
|
4354
|
+
import { realpathSync } from "fs";
|
|
4355
|
+
import { z as z8 } from "zod";
|
|
4356
|
+
function deriveClaudeStatus(state, waitingForRetry) {
|
|
4357
|
+
if (waitingForRetry) return "waiting";
|
|
4358
|
+
if (!state) return "idle";
|
|
4359
|
+
if (WORKING_STATES.has(state)) return "working";
|
|
4360
|
+
if (IDLE_STATES.has(state)) return "idle";
|
|
4361
|
+
return "unknown";
|
|
4362
|
+
}
|
|
4363
|
+
function deriveWorkspaceId(projectRoot) {
|
|
4364
|
+
return realpathSync(projectRoot);
|
|
4365
|
+
}
|
|
4366
|
+
var WORKING_STATES, IDLE_STATES, CURRENT_STATUS_SCHEMA_VERSION, WORKFLOW_STATES, WorkflowStateSchema, CURRENT_SESSION_SCHEMA_VERSION, SessionStateSchema;
|
|
4367
|
+
var init_session_types = __esm({
|
|
4368
|
+
"src/autonomous/session-types.ts"() {
|
|
4369
|
+
"use strict";
|
|
4370
|
+
init_esm_shims();
|
|
4371
|
+
WORKING_STATES = /* @__PURE__ */ new Set([
|
|
4372
|
+
"PLAN",
|
|
4373
|
+
"PLAN_REVIEW",
|
|
4374
|
+
"IMPLEMENT",
|
|
4375
|
+
"CODE_REVIEW",
|
|
4376
|
+
"FINALIZE",
|
|
4377
|
+
"COMPACT"
|
|
4378
|
+
]);
|
|
4379
|
+
IDLE_STATES = /* @__PURE__ */ new Set([
|
|
4380
|
+
"INIT",
|
|
4381
|
+
"LOAD_CONTEXT",
|
|
4382
|
+
"PICK_TICKET",
|
|
4383
|
+
"HANDOVER",
|
|
4384
|
+
"COMPLETE",
|
|
4385
|
+
"SESSION_END"
|
|
4386
|
+
]);
|
|
4387
|
+
CURRENT_STATUS_SCHEMA_VERSION = 1;
|
|
4388
|
+
WORKFLOW_STATES = [
|
|
4389
|
+
"INIT",
|
|
4390
|
+
"LOAD_CONTEXT",
|
|
4391
|
+
"PICK_TICKET",
|
|
4392
|
+
"PLAN",
|
|
4393
|
+
"PLAN_REVIEW",
|
|
4394
|
+
"IMPLEMENT",
|
|
4395
|
+
"CODE_REVIEW",
|
|
4396
|
+
"FINALIZE",
|
|
4397
|
+
"COMPACT",
|
|
4398
|
+
"HANDOVER",
|
|
4399
|
+
"COMPLETE",
|
|
4400
|
+
"SESSION_END"
|
|
4401
|
+
];
|
|
4402
|
+
WorkflowStateSchema = z8.enum(WORKFLOW_STATES);
|
|
4403
|
+
CURRENT_SESSION_SCHEMA_VERSION = 1;
|
|
4404
|
+
SessionStateSchema = z8.object({
|
|
4405
|
+
schemaVersion: z8.literal(CURRENT_SESSION_SCHEMA_VERSION),
|
|
4406
|
+
sessionId: z8.string().uuid(),
|
|
4407
|
+
recipe: z8.string(),
|
|
4408
|
+
state: z8.string(),
|
|
4409
|
+
previousState: z8.string().optional(),
|
|
4410
|
+
revision: z8.number().int().min(0),
|
|
4411
|
+
status: z8.enum(["active", "completed", "superseded"]).default("active"),
|
|
4412
|
+
// Ticket in progress
|
|
4413
|
+
ticket: z8.object({
|
|
4414
|
+
id: z8.string(),
|
|
4415
|
+
title: z8.string(),
|
|
4416
|
+
risk: z8.string().optional(),
|
|
4417
|
+
realizedRisk: z8.string().optional(),
|
|
4418
|
+
claimed: z8.boolean().default(false)
|
|
4419
|
+
}).optional(),
|
|
4420
|
+
// Review tracking
|
|
4421
|
+
reviews: z8.object({
|
|
4422
|
+
plan: z8.array(z8.object({
|
|
4423
|
+
round: z8.number(),
|
|
4424
|
+
reviewer: z8.string(),
|
|
4425
|
+
verdict: z8.string(),
|
|
4426
|
+
findingCount: z8.number(),
|
|
4427
|
+
criticalCount: z8.number(),
|
|
4428
|
+
majorCount: z8.number(),
|
|
4429
|
+
suggestionCount: z8.number(),
|
|
4430
|
+
codexSessionId: z8.string().optional(),
|
|
4431
|
+
timestamp: z8.string()
|
|
4432
|
+
})).default([]),
|
|
4433
|
+
code: z8.array(z8.object({
|
|
4434
|
+
round: z8.number(),
|
|
4435
|
+
reviewer: z8.string(),
|
|
4436
|
+
verdict: z8.string(),
|
|
4437
|
+
findingCount: z8.number(),
|
|
4438
|
+
criticalCount: z8.number(),
|
|
4439
|
+
majorCount: z8.number(),
|
|
4440
|
+
suggestionCount: z8.number(),
|
|
4441
|
+
codexSessionId: z8.string().optional(),
|
|
4442
|
+
timestamp: z8.string()
|
|
4443
|
+
})).default([])
|
|
4444
|
+
}).default({ plan: [], code: [] }),
|
|
4445
|
+
// Completed tickets this session
|
|
4446
|
+
completedTickets: z8.array(z8.object({
|
|
4447
|
+
id: z8.string(),
|
|
4448
|
+
title: z8.string().optional(),
|
|
4449
|
+
commitHash: z8.string().optional(),
|
|
4450
|
+
risk: z8.string().optional()
|
|
4451
|
+
})).default([]),
|
|
4452
|
+
// FINALIZE checkpoint
|
|
4453
|
+
finalizeCheckpoint: z8.enum(["staged", "precommit_passed", "committed"]).nullable().default(null),
|
|
4454
|
+
// Git state
|
|
4455
|
+
git: z8.object({
|
|
4456
|
+
branch: z8.string().nullable().default(null),
|
|
4457
|
+
initHead: z8.string().optional(),
|
|
4458
|
+
mergeBase: z8.string().nullable().default(null),
|
|
4459
|
+
expectedHead: z8.string().optional(),
|
|
4460
|
+
baseline: z8.object({
|
|
4461
|
+
porcelain: z8.array(z8.string()).default([]),
|
|
4462
|
+
dirtyTrackedFiles: z8.record(z8.object({ blobHash: z8.string() })).default({}),
|
|
4463
|
+
untrackedPaths: z8.array(z8.string()).default([])
|
|
4464
|
+
}).optional()
|
|
4465
|
+
}).default({ branch: null, mergeBase: null }),
|
|
4466
|
+
// Lease
|
|
4467
|
+
lease: z8.object({
|
|
4468
|
+
workspaceId: z8.string().optional(),
|
|
4469
|
+
lastHeartbeat: z8.string(),
|
|
4470
|
+
expiresAt: z8.string()
|
|
4471
|
+
}),
|
|
4472
|
+
// Context pressure
|
|
4473
|
+
contextPressure: z8.object({
|
|
4474
|
+
level: z8.string().default("low"),
|
|
4475
|
+
guideCallCount: z8.number().default(0),
|
|
4476
|
+
ticketsCompleted: z8.number().default(0),
|
|
4477
|
+
compactionCount: z8.number().default(0),
|
|
4478
|
+
eventsLogBytes: z8.number().default(0)
|
|
4479
|
+
}).default({ level: "low", guideCallCount: 0, ticketsCompleted: 0, compactionCount: 0, eventsLogBytes: 0 }),
|
|
4480
|
+
// Pending project mutation (for crash recovery)
|
|
4481
|
+
pendingProjectMutation: z8.any().nullable().default(null),
|
|
4482
|
+
// COMPACT resume
|
|
4483
|
+
resumeFromRevision: z8.number().nullable().default(null),
|
|
4484
|
+
preCompactState: z8.string().nullable().default(null),
|
|
4485
|
+
// Session metadata
|
|
4486
|
+
waitingForRetry: z8.boolean().default(false),
|
|
4487
|
+
lastGuideCall: z8.string().optional(),
|
|
4488
|
+
startedAt: z8.string(),
|
|
4489
|
+
guideCallCount: z8.number().default(0),
|
|
4490
|
+
// Supersession tracking
|
|
4491
|
+
supersededBy: z8.string().optional(),
|
|
4492
|
+
supersededSession: z8.string().optional(),
|
|
4493
|
+
stealReason: z8.string().optional(),
|
|
4494
|
+
// Recipe overrides
|
|
4495
|
+
config: z8.object({
|
|
4496
|
+
maxTicketsPerSession: z8.number().default(3),
|
|
4497
|
+
compactThreshold: z8.string().default("high"),
|
|
4498
|
+
reviewBackends: z8.array(z8.string()).default(["codex", "agent"])
|
|
4499
|
+
}).default({ maxTicketsPerSession: 3, compactThreshold: "high", reviewBackends: ["codex", "agent"] })
|
|
4500
|
+
}).passthrough();
|
|
4501
|
+
}
|
|
4502
|
+
});
|
|
4503
|
+
|
|
4504
|
+
// src/autonomous/session.ts
|
|
4505
|
+
import { randomUUID } from "crypto";
|
|
4506
|
+
import {
|
|
4507
|
+
mkdirSync,
|
|
4508
|
+
readdirSync,
|
|
4509
|
+
readFileSync,
|
|
4510
|
+
writeFileSync,
|
|
4511
|
+
renameSync,
|
|
4512
|
+
unlinkSync,
|
|
4513
|
+
existsSync as existsSync6,
|
|
4514
|
+
rmSync
|
|
4515
|
+
} from "fs";
|
|
4516
|
+
import { join as join6 } from "path";
|
|
4517
|
+
import lockfile2 from "proper-lockfile";
|
|
4518
|
+
function sessionsRoot(root) {
|
|
4519
|
+
return join6(root, ".story", SESSIONS_DIR);
|
|
4520
|
+
}
|
|
4521
|
+
function sessionDir(root, sessionId) {
|
|
4522
|
+
return join6(sessionsRoot(root), sessionId);
|
|
4523
|
+
}
|
|
4524
|
+
function statePath(dir) {
|
|
4525
|
+
return join6(dir, "state.json");
|
|
4526
|
+
}
|
|
4527
|
+
function eventsPath(dir) {
|
|
4528
|
+
return join6(dir, "events.log");
|
|
4529
|
+
}
|
|
4530
|
+
function createSession(root, recipe, workspaceId) {
|
|
4531
|
+
const id = randomUUID();
|
|
4532
|
+
const dir = sessionDir(root, id);
|
|
4533
|
+
mkdirSync(dir, { recursive: true });
|
|
4534
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4535
|
+
const state = {
|
|
4536
|
+
schemaVersion: CURRENT_SESSION_SCHEMA_VERSION,
|
|
4537
|
+
sessionId: id,
|
|
4538
|
+
recipe,
|
|
4539
|
+
state: "INIT",
|
|
4540
|
+
revision: 0,
|
|
4541
|
+
status: "active",
|
|
4542
|
+
reviews: { plan: [], code: [] },
|
|
4543
|
+
completedTickets: [],
|
|
4544
|
+
finalizeCheckpoint: null,
|
|
4545
|
+
git: { branch: null, mergeBase: null },
|
|
4546
|
+
lease: {
|
|
4547
|
+
workspaceId,
|
|
4548
|
+
lastHeartbeat: now,
|
|
4549
|
+
expiresAt: new Date(Date.now() + LEASE_DURATION_MS).toISOString()
|
|
4550
|
+
},
|
|
4551
|
+
contextPressure: {
|
|
4552
|
+
level: "low",
|
|
4553
|
+
guideCallCount: 0,
|
|
4554
|
+
ticketsCompleted: 0,
|
|
4555
|
+
compactionCount: 0,
|
|
4556
|
+
eventsLogBytes: 0
|
|
4557
|
+
},
|
|
4558
|
+
pendingProjectMutation: null,
|
|
4559
|
+
resumeFromRevision: null,
|
|
4560
|
+
preCompactState: null,
|
|
4561
|
+
waitingForRetry: false,
|
|
4562
|
+
lastGuideCall: now,
|
|
4563
|
+
startedAt: now,
|
|
4564
|
+
guideCallCount: 0,
|
|
4565
|
+
config: {
|
|
4566
|
+
maxTicketsPerSession: 3,
|
|
4567
|
+
compactThreshold: "high",
|
|
4568
|
+
reviewBackends: ["codex", "agent"]
|
|
4569
|
+
}
|
|
4570
|
+
};
|
|
4571
|
+
writeSessionSync(dir, state);
|
|
4572
|
+
return state;
|
|
4573
|
+
}
|
|
4574
|
+
function readSession(dir) {
|
|
4575
|
+
const path2 = statePath(dir);
|
|
4576
|
+
let raw;
|
|
4577
|
+
try {
|
|
4578
|
+
raw = readFileSync(path2, "utf-8");
|
|
4579
|
+
} catch {
|
|
4580
|
+
return null;
|
|
4581
|
+
}
|
|
4582
|
+
let parsed;
|
|
4583
|
+
try {
|
|
4584
|
+
parsed = JSON.parse(raw);
|
|
4585
|
+
} catch {
|
|
4586
|
+
return null;
|
|
4587
|
+
}
|
|
4588
|
+
const result = SessionStateSchema.safeParse(parsed);
|
|
4589
|
+
if (!result.success) return null;
|
|
4590
|
+
return result.data;
|
|
4591
|
+
}
|
|
4592
|
+
function writeSessionSync(dir, state) {
|
|
4593
|
+
const path2 = statePath(dir);
|
|
4594
|
+
const updated = { ...state, revision: state.revision + 1 };
|
|
4595
|
+
const content = JSON.stringify(updated, null, 2) + "\n";
|
|
4596
|
+
const tmp = `${path2}.${process.pid}.tmp`;
|
|
4597
|
+
try {
|
|
4598
|
+
writeFileSync(tmp, content, "utf-8");
|
|
4599
|
+
renameSync(tmp, path2);
|
|
4600
|
+
} catch (err) {
|
|
4601
|
+
try {
|
|
4602
|
+
unlinkSync(tmp);
|
|
4603
|
+
} catch {
|
|
4604
|
+
}
|
|
4605
|
+
throw err;
|
|
4606
|
+
}
|
|
4607
|
+
return updated;
|
|
4608
|
+
}
|
|
4609
|
+
function appendEvent(dir, event) {
|
|
4610
|
+
try {
|
|
4611
|
+
const path2 = eventsPath(dir);
|
|
4612
|
+
const line = JSON.stringify(event) + "\n";
|
|
4613
|
+
writeFileSync(path2, line, { flag: "a", encoding: "utf-8" });
|
|
4614
|
+
} catch {
|
|
4615
|
+
}
|
|
4616
|
+
}
|
|
4617
|
+
function deleteSession(root, sessionId) {
|
|
4618
|
+
const dir = sessionDir(root, sessionId);
|
|
4619
|
+
try {
|
|
4620
|
+
rmSync(dir, { recursive: true, force: true });
|
|
4621
|
+
} catch {
|
|
4622
|
+
}
|
|
4623
|
+
}
|
|
4624
|
+
function refreshLease(state) {
|
|
4625
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4626
|
+
const newCallCount = state.guideCallCount + 1;
|
|
4627
|
+
return {
|
|
4628
|
+
...state,
|
|
4629
|
+
lease: {
|
|
4630
|
+
...state.lease,
|
|
4631
|
+
lastHeartbeat: now,
|
|
4632
|
+
expiresAt: new Date(Date.now() + LEASE_DURATION_MS).toISOString()
|
|
4633
|
+
},
|
|
4634
|
+
lastGuideCall: now,
|
|
4635
|
+
guideCallCount: newCallCount,
|
|
4636
|
+
contextPressure: {
|
|
4637
|
+
...state.contextPressure,
|
|
4638
|
+
guideCallCount: newCallCount,
|
|
4639
|
+
ticketsCompleted: state.completedTickets?.length ?? 0
|
|
4640
|
+
}
|
|
4641
|
+
};
|
|
4642
|
+
}
|
|
4643
|
+
function isLeaseExpired(state) {
|
|
4644
|
+
if (!state.lease?.expiresAt) return true;
|
|
4645
|
+
const expires = new Date(state.lease.expiresAt).getTime();
|
|
4646
|
+
return Number.isNaN(expires) || expires <= Date.now();
|
|
4647
|
+
}
|
|
4648
|
+
function findActiveSessionFull(root) {
|
|
4649
|
+
const sessDir = sessionsRoot(root);
|
|
4650
|
+
let entries;
|
|
4651
|
+
try {
|
|
4652
|
+
entries = readdirSync(sessDir, { withFileTypes: true });
|
|
4653
|
+
} catch {
|
|
4654
|
+
return null;
|
|
4655
|
+
}
|
|
4656
|
+
let workspaceId;
|
|
4657
|
+
try {
|
|
4658
|
+
workspaceId = deriveWorkspaceId(root);
|
|
4659
|
+
} catch {
|
|
4660
|
+
return null;
|
|
4661
|
+
}
|
|
4662
|
+
let best = null;
|
|
4663
|
+
let bestGuideCall = 0;
|
|
4664
|
+
for (const entry of entries) {
|
|
4665
|
+
if (!entry.isDirectory()) continue;
|
|
4666
|
+
const dir = join6(sessDir, entry.name);
|
|
4667
|
+
const session = readSession(dir);
|
|
4668
|
+
if (!session) continue;
|
|
4669
|
+
if (session.status !== "active") continue;
|
|
4670
|
+
if (session.lease?.workspaceId && session.lease.workspaceId !== workspaceId) continue;
|
|
4671
|
+
if (isLeaseExpired(session)) continue;
|
|
4672
|
+
const guideCall = session.lastGuideCall ? new Date(session.lastGuideCall).getTime() : 0;
|
|
4673
|
+
const guideCallValid = Number.isNaN(guideCall) ? 0 : guideCall;
|
|
4674
|
+
if (!best || guideCallValid > bestGuideCall || guideCallValid === bestGuideCall && session.sessionId > best.state.sessionId) {
|
|
4675
|
+
best = { state: session, dir };
|
|
4676
|
+
bestGuideCall = guideCallValid;
|
|
4677
|
+
}
|
|
4678
|
+
}
|
|
4679
|
+
return best;
|
|
4680
|
+
}
|
|
4681
|
+
function findActiveSessionMinimal(root) {
|
|
4682
|
+
const result = findActiveSessionFull(root);
|
|
4683
|
+
return result?.state ?? null;
|
|
4684
|
+
}
|
|
4685
|
+
function findStaleSessions(root) {
|
|
4686
|
+
const sessDir = sessionsRoot(root);
|
|
4687
|
+
let entries;
|
|
4688
|
+
try {
|
|
4689
|
+
entries = readdirSync(sessDir, { withFileTypes: true });
|
|
4690
|
+
} catch {
|
|
4691
|
+
return [];
|
|
4692
|
+
}
|
|
4693
|
+
let workspaceId;
|
|
4694
|
+
try {
|
|
4695
|
+
workspaceId = deriveWorkspaceId(root);
|
|
4696
|
+
} catch {
|
|
4697
|
+
return [];
|
|
4698
|
+
}
|
|
4699
|
+
const results = [];
|
|
4700
|
+
for (const entry of entries) {
|
|
4701
|
+
if (!entry.isDirectory()) continue;
|
|
4702
|
+
const dir = join6(sessDir, entry.name);
|
|
4703
|
+
const session = readSession(dir);
|
|
4704
|
+
if (!session) continue;
|
|
4705
|
+
if (session.status !== "active") continue;
|
|
4706
|
+
if (session.lease?.workspaceId && session.lease.workspaceId !== workspaceId) continue;
|
|
4707
|
+
if (isLeaseExpired(session)) {
|
|
4708
|
+
results.push({ state: session, dir });
|
|
4709
|
+
}
|
|
4710
|
+
}
|
|
4711
|
+
return results;
|
|
4712
|
+
}
|
|
4713
|
+
function findSessionById(root, sessionId) {
|
|
4714
|
+
const dir = sessionDir(root, sessionId);
|
|
4715
|
+
if (!existsSync6(dir)) return null;
|
|
4716
|
+
const state = readSession(dir);
|
|
4717
|
+
if (!state) return null;
|
|
4718
|
+
return { state, dir };
|
|
4719
|
+
}
|
|
4720
|
+
async function withSessionLock(root, fn) {
|
|
4721
|
+
const sessDir = sessionsRoot(root);
|
|
4722
|
+
mkdirSync(sessDir, { recursive: true });
|
|
4723
|
+
let release;
|
|
4724
|
+
try {
|
|
4725
|
+
release = await lockfile2.lock(sessDir, {
|
|
4726
|
+
retries: { retries: 3, minTimeout: 100, maxTimeout: 1e3 },
|
|
4727
|
+
stale: 3e4,
|
|
4728
|
+
lockfilePath: join6(sessDir, ".lock")
|
|
4729
|
+
});
|
|
4730
|
+
return await fn();
|
|
4731
|
+
} finally {
|
|
4732
|
+
if (release) {
|
|
4733
|
+
try {
|
|
4734
|
+
await release();
|
|
4735
|
+
} catch {
|
|
4736
|
+
}
|
|
4737
|
+
}
|
|
4738
|
+
}
|
|
4739
|
+
}
|
|
4740
|
+
var LEASE_DURATION_MS, SESSIONS_DIR;
|
|
4741
|
+
var init_session = __esm({
|
|
4742
|
+
"src/autonomous/session.ts"() {
|
|
4743
|
+
"use strict";
|
|
4744
|
+
init_esm_shims();
|
|
4745
|
+
init_session_types();
|
|
4746
|
+
LEASE_DURATION_MS = 45 * 60 * 1e3;
|
|
4747
|
+
SESSIONS_DIR = "sessions";
|
|
4748
|
+
}
|
|
4749
|
+
});
|
|
4750
|
+
|
|
4751
|
+
// src/autonomous/state-machine.ts
|
|
4752
|
+
var init_state_machine = __esm({
|
|
4753
|
+
"src/autonomous/state-machine.ts"() {
|
|
4754
|
+
"use strict";
|
|
4755
|
+
init_esm_shims();
|
|
4756
|
+
}
|
|
4757
|
+
});
|
|
4758
|
+
|
|
4759
|
+
// src/autonomous/context-pressure.ts
|
|
4760
|
+
function evaluatePressure(state) {
|
|
4761
|
+
const calls = state.contextPressure?.guideCallCount ?? state.guideCallCount ?? 0;
|
|
4762
|
+
const tickets = state.contextPressure?.ticketsCompleted ?? state.completedTickets?.length ?? 0;
|
|
4763
|
+
const eventsBytes = state.contextPressure?.eventsLogBytes ?? 0;
|
|
4764
|
+
if (calls > 45 || tickets >= 3 || eventsBytes > 5e5) return "critical";
|
|
4765
|
+
if (calls >= 30 || tickets >= 2 || eventsBytes > 2e5) return "high";
|
|
4766
|
+
if (calls >= 15 || tickets >= 1 || eventsBytes > 5e4) return "medium";
|
|
4767
|
+
return "low";
|
|
4768
|
+
}
|
|
4769
|
+
var init_context_pressure = __esm({
|
|
4770
|
+
"src/autonomous/context-pressure.ts"() {
|
|
4771
|
+
"use strict";
|
|
4772
|
+
init_esm_shims();
|
|
4773
|
+
}
|
|
4774
|
+
});
|
|
4775
|
+
|
|
4776
|
+
// src/autonomous/review-depth.ts
|
|
4777
|
+
function assessRisk(diffStats, changedFiles) {
|
|
4778
|
+
let level = "low";
|
|
4779
|
+
if (diffStats) {
|
|
4780
|
+
const total = diffStats.totalLines;
|
|
4781
|
+
if (total > 200) level = "high";
|
|
4782
|
+
else if (total >= 50) level = "medium";
|
|
4783
|
+
}
|
|
4784
|
+
if (changedFiles && level !== "high") {
|
|
4785
|
+
const hasSensitive = changedFiles.some(
|
|
4786
|
+
(f) => SENSITIVE_PATTERNS.some((p) => p.test(f))
|
|
4787
|
+
);
|
|
4788
|
+
if (hasSensitive) {
|
|
4789
|
+
level = level === "low" ? "medium" : "high";
|
|
4790
|
+
}
|
|
4791
|
+
}
|
|
4792
|
+
return level;
|
|
4793
|
+
}
|
|
4794
|
+
function requiredRounds(risk) {
|
|
4795
|
+
switch (risk) {
|
|
4796
|
+
case "low":
|
|
4797
|
+
return 1;
|
|
4798
|
+
case "medium":
|
|
4799
|
+
return 2;
|
|
4800
|
+
case "high":
|
|
4801
|
+
return 3;
|
|
4802
|
+
}
|
|
4803
|
+
}
|
|
4804
|
+
function nextReviewer(previousRounds, backends) {
|
|
4805
|
+
if (backends.length === 0) return "agent";
|
|
4806
|
+
if (backends.length === 1) return backends[0];
|
|
4807
|
+
if (previousRounds.length === 0) return backends[0];
|
|
4808
|
+
const lastReviewer = previousRounds[previousRounds.length - 1].reviewer;
|
|
4809
|
+
const lastIndex = backends.indexOf(lastReviewer);
|
|
4810
|
+
if (lastIndex === -1) return backends[0];
|
|
4811
|
+
return backends[(lastIndex + 1) % backends.length];
|
|
4812
|
+
}
|
|
4813
|
+
var SENSITIVE_PATTERNS;
|
|
4814
|
+
var init_review_depth = __esm({
|
|
4815
|
+
"src/autonomous/review-depth.ts"() {
|
|
4816
|
+
"use strict";
|
|
4817
|
+
init_esm_shims();
|
|
4818
|
+
SENSITIVE_PATTERNS = [
|
|
4819
|
+
/\bauth\b/i,
|
|
4820
|
+
/\bsecurity\b/i,
|
|
4821
|
+
/\bmigration/i,
|
|
4822
|
+
/\bconfig\b/i,
|
|
4823
|
+
/\bmiddleware\b/i,
|
|
4824
|
+
/\.env/i
|
|
4825
|
+
];
|
|
4826
|
+
}
|
|
4827
|
+
});
|
|
4828
|
+
|
|
4829
|
+
// src/autonomous/git-inspector.ts
|
|
4830
|
+
import { execFile } from "child_process";
|
|
4831
|
+
async function git(cwd, args, parse) {
|
|
4832
|
+
return new Promise((resolve9) => {
|
|
4833
|
+
execFile("git", args, { cwd, timeout: GIT_TIMEOUT, maxBuffer: 10 * 1024 * 1024 }, (err, stdout, stderr) => {
|
|
4834
|
+
if (err) {
|
|
4835
|
+
const message = stderr?.trim() || err.message || "unknown git error";
|
|
4836
|
+
resolve9({ ok: false, reason: "git_error", message });
|
|
4837
|
+
return;
|
|
4838
|
+
}
|
|
4839
|
+
try {
|
|
4840
|
+
resolve9({ ok: true, data: parse(stdout) });
|
|
4841
|
+
} catch (parseErr) {
|
|
4842
|
+
resolve9({ ok: false, reason: "parse_error", message: parseErr.message });
|
|
4843
|
+
}
|
|
4844
|
+
});
|
|
4845
|
+
});
|
|
4846
|
+
}
|
|
4847
|
+
async function gitStatus(cwd) {
|
|
4848
|
+
return git(
|
|
4849
|
+
cwd,
|
|
4850
|
+
["status", "--porcelain"],
|
|
4851
|
+
(out) => out.split("\n").filter((l) => l.length > 0)
|
|
4852
|
+
);
|
|
4853
|
+
}
|
|
4854
|
+
async function gitHead(cwd) {
|
|
4855
|
+
const hashResult = await git(cwd, ["rev-parse", "HEAD"], (out) => out.trim());
|
|
4856
|
+
if (!hashResult.ok) return hashResult;
|
|
4857
|
+
const branchResult = await gitBranch(cwd);
|
|
4858
|
+
return {
|
|
4859
|
+
ok: true,
|
|
4860
|
+
data: {
|
|
4861
|
+
hash: hashResult.data,
|
|
4862
|
+
branch: branchResult.ok ? branchResult.data : null
|
|
4863
|
+
}
|
|
4864
|
+
};
|
|
4865
|
+
}
|
|
4866
|
+
async function gitBranch(cwd) {
|
|
4867
|
+
return git(cwd, ["symbolic-ref", "--short", "HEAD"], (out) => out.trim());
|
|
4868
|
+
}
|
|
4869
|
+
async function gitMergeBase(cwd, base) {
|
|
4870
|
+
return git(cwd, ["merge-base", "HEAD", base], (out) => out.trim());
|
|
4871
|
+
}
|
|
4872
|
+
async function gitDiffStat(cwd, base) {
|
|
4873
|
+
return git(cwd, ["diff", "--numstat", base], parseDiffNumstat);
|
|
4874
|
+
}
|
|
4875
|
+
async function gitDiffNames(cwd, base) {
|
|
4876
|
+
return git(
|
|
4877
|
+
cwd,
|
|
4878
|
+
["diff", "--name-only", base],
|
|
4879
|
+
(out) => out.split("\n").filter((l) => l.length > 0)
|
|
4880
|
+
);
|
|
4881
|
+
}
|
|
4882
|
+
async function gitBlobHash(cwd, file) {
|
|
4883
|
+
return git(cwd, ["hash-object", file], (out) => out.trim());
|
|
4884
|
+
}
|
|
4885
|
+
async function gitDiffCachedNames(cwd) {
|
|
4886
|
+
return git(
|
|
4887
|
+
cwd,
|
|
4888
|
+
["diff", "--cached", "--name-only"],
|
|
4889
|
+
(out) => out.split("\n").filter((l) => l.length > 0)
|
|
4890
|
+
);
|
|
4891
|
+
}
|
|
4892
|
+
function parseDiffNumstat(out) {
|
|
4893
|
+
const lines = out.split("\n").filter((l) => l.length > 0);
|
|
4894
|
+
let insertions = 0;
|
|
4895
|
+
let deletions = 0;
|
|
4896
|
+
let filesChanged = 0;
|
|
4897
|
+
for (const line of lines) {
|
|
4898
|
+
const parts = line.split(" ");
|
|
4899
|
+
if (parts.length < 3) continue;
|
|
4900
|
+
const added = parseInt(parts[0], 10);
|
|
4901
|
+
const removed = parseInt(parts[1], 10);
|
|
4902
|
+
if (!Number.isNaN(added)) insertions += added;
|
|
4903
|
+
if (!Number.isNaN(removed)) deletions += removed;
|
|
4904
|
+
filesChanged++;
|
|
4905
|
+
}
|
|
4906
|
+
return { filesChanged, insertions, deletions, totalLines: insertions + deletions };
|
|
4907
|
+
}
|
|
4908
|
+
var GIT_TIMEOUT;
|
|
4909
|
+
var init_git_inspector = __esm({
|
|
4910
|
+
"src/autonomous/git-inspector.ts"() {
|
|
4911
|
+
"use strict";
|
|
4912
|
+
init_esm_shims();
|
|
4913
|
+
GIT_TIMEOUT = 1e4;
|
|
4914
|
+
}
|
|
4915
|
+
});
|
|
4916
|
+
|
|
4917
|
+
// src/autonomous/guide.ts
|
|
4918
|
+
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync as existsSync7 } from "fs";
|
|
4919
|
+
import { join as join7 } from "path";
|
|
4920
|
+
async function handleAutonomousGuide(root, args) {
|
|
4921
|
+
const wsId = deriveWorkspaceId(root);
|
|
4922
|
+
const prev = workspaceLocks.get(wsId) ?? Promise.resolve();
|
|
4923
|
+
const current = prev.then(async () => {
|
|
4924
|
+
return withSessionLock(root, () => handleGuideInner(root, args));
|
|
4925
|
+
});
|
|
4926
|
+
workspaceLocks.set(wsId, current.then(() => {
|
|
4927
|
+
}, () => {
|
|
4928
|
+
}));
|
|
4929
|
+
try {
|
|
4930
|
+
return await current;
|
|
4931
|
+
} catch (err) {
|
|
4932
|
+
return guideError(err);
|
|
4933
|
+
} finally {
|
|
4934
|
+
const stored = workspaceLocks.get(wsId);
|
|
4935
|
+
if (stored) {
|
|
4936
|
+
stored.then(() => {
|
|
4937
|
+
if (workspaceLocks.get(wsId) === stored) {
|
|
4938
|
+
workspaceLocks.delete(wsId);
|
|
4939
|
+
}
|
|
4940
|
+
}, () => {
|
|
4941
|
+
if (workspaceLocks.get(wsId) === stored) {
|
|
4942
|
+
workspaceLocks.delete(wsId);
|
|
4943
|
+
}
|
|
4944
|
+
});
|
|
4945
|
+
}
|
|
4946
|
+
}
|
|
4947
|
+
}
|
|
4948
|
+
async function handleGuideInner(root, args) {
|
|
4949
|
+
switch (args.action) {
|
|
4950
|
+
case "start":
|
|
4951
|
+
return handleStart(root, args);
|
|
4952
|
+
case "report":
|
|
4953
|
+
return handleReport(root, args);
|
|
4954
|
+
case "resume":
|
|
4955
|
+
return handleResume(root, args);
|
|
4956
|
+
case "pre_compact":
|
|
4957
|
+
return handlePreCompact(root, args);
|
|
4958
|
+
case "cancel":
|
|
4959
|
+
return handleCancel(root, args);
|
|
4960
|
+
default:
|
|
4961
|
+
return guideError(new Error(`Unknown action: ${args.action}`));
|
|
4962
|
+
}
|
|
4963
|
+
}
|
|
4964
|
+
async function handleStart(root, args) {
|
|
4965
|
+
const existing = findActiveSessionFull(root);
|
|
4966
|
+
if (existing && !isLeaseExpired(existing.state)) {
|
|
4967
|
+
return guideError(new Error(
|
|
4968
|
+
`Active session ${existing.state.sessionId} already exists for this workspace. Use action: "resume" to continue or "cancel" to end it.`
|
|
4969
|
+
));
|
|
4970
|
+
}
|
|
4971
|
+
const staleSessions = findStaleSessions(root);
|
|
4972
|
+
for (const stale of staleSessions) {
|
|
4973
|
+
writeSessionSync(stale.dir, { ...stale.state, status: "superseded" });
|
|
4974
|
+
}
|
|
4975
|
+
const wsId = deriveWorkspaceId(root);
|
|
4976
|
+
const recipe = "coding";
|
|
4977
|
+
const session = createSession(root, recipe, wsId);
|
|
4978
|
+
const dir = sessionDir(root, session.sessionId);
|
|
4979
|
+
try {
|
|
4980
|
+
const headResult = await gitHead(root);
|
|
4981
|
+
if (!headResult.ok) {
|
|
4982
|
+
deleteSession(root, session.sessionId);
|
|
4983
|
+
return guideError(new Error("This directory is not a git repository or git is not available. Autonomous mode requires git."));
|
|
4984
|
+
}
|
|
4985
|
+
const stagedResult = await gitDiffCachedNames(root);
|
|
4986
|
+
if (stagedResult.ok && stagedResult.data.length > 0) {
|
|
4987
|
+
deleteSession(root, session.sessionId);
|
|
4988
|
+
return guideError(new Error(
|
|
4989
|
+
`Cannot start: ${stagedResult.data.length} staged file(s). Unstage with \`git restore --staged .\` or commit them first, then call start again.
|
|
4990
|
+
|
|
4991
|
+
Staged: ${stagedResult.data.join(", ")}`
|
|
4992
|
+
));
|
|
4993
|
+
}
|
|
4994
|
+
const statusResult = await gitStatus(root);
|
|
4995
|
+
let mergeBaseResult = await gitMergeBase(root, "main");
|
|
4996
|
+
if (!mergeBaseResult.ok) mergeBaseResult = await gitMergeBase(root, "master");
|
|
4997
|
+
const porcelainLines = statusResult.ok ? statusResult.data : [];
|
|
4998
|
+
const dirtyTracked = {};
|
|
4999
|
+
const untrackedPaths = [];
|
|
5000
|
+
for (const line of porcelainLines) {
|
|
5001
|
+
if (line.startsWith("??")) {
|
|
5002
|
+
untrackedPaths.push(line.slice(3).trim());
|
|
5003
|
+
} else if (line.length > 3) {
|
|
5004
|
+
const filePath = line.slice(3).trim();
|
|
5005
|
+
const hashResult = await gitBlobHash(root, filePath);
|
|
5006
|
+
dirtyTracked[filePath] = { blobHash: hashResult.ok ? hashResult.data : "" };
|
|
5007
|
+
}
|
|
5008
|
+
}
|
|
5009
|
+
if (Object.keys(dirtyTracked).length > 0) {
|
|
5010
|
+
deleteSession(root, session.sessionId);
|
|
5011
|
+
const dirtyFiles = Object.keys(dirtyTracked).join(", ");
|
|
5012
|
+
return guideError(new Error(
|
|
5013
|
+
`Cannot start: ${Object.keys(dirtyTracked).length} dirty tracked file(s): ${dirtyFiles}. Create a feature branch or stash changes first, then call start again.`
|
|
5014
|
+
));
|
|
5015
|
+
}
|
|
5016
|
+
let updated = {
|
|
5017
|
+
...session,
|
|
5018
|
+
state: "PICK_TICKET",
|
|
5019
|
+
previousState: "INIT",
|
|
5020
|
+
git: {
|
|
5021
|
+
branch: headResult.data.branch,
|
|
5022
|
+
initHead: headResult.data.hash,
|
|
5023
|
+
mergeBase: mergeBaseResult.ok ? mergeBaseResult.data : null,
|
|
5024
|
+
expectedHead: headResult.data.hash,
|
|
5025
|
+
baseline: {
|
|
5026
|
+
porcelain: porcelainLines,
|
|
5027
|
+
dirtyTrackedFiles: dirtyTracked,
|
|
5028
|
+
untrackedPaths
|
|
5029
|
+
}
|
|
5030
|
+
}
|
|
5031
|
+
};
|
|
5032
|
+
const { state: projectState, warnings } = await loadProject(root);
|
|
5033
|
+
const handoversDir = join7(root, ".story", "handovers");
|
|
5034
|
+
const ctx = { state: projectState, warnings, root, handoversDir, format: "md" };
|
|
5035
|
+
let handoverText = "";
|
|
5036
|
+
try {
|
|
5037
|
+
const handoverResult = await handleHandoverLatest(ctx, 3);
|
|
5038
|
+
handoverText = handoverResult.output;
|
|
5039
|
+
} catch {
|
|
5040
|
+
}
|
|
5041
|
+
let recapText = "";
|
|
5042
|
+
try {
|
|
5043
|
+
const snapshotInfo = await loadLatestSnapshot(root);
|
|
5044
|
+
const recap = buildRecap(projectState, snapshotInfo);
|
|
5045
|
+
if (recap.changes) {
|
|
5046
|
+
recapText = "Changes since last snapshot available.";
|
|
5047
|
+
}
|
|
5048
|
+
} catch {
|
|
5049
|
+
}
|
|
5050
|
+
const rulesText = readFileSafe(join7(root, "RULES.md"));
|
|
5051
|
+
const strategiesText = readFileSafe(join7(root, "WORK_STRATEGIES.md"));
|
|
5052
|
+
const digestParts = [
|
|
5053
|
+
handoverText ? `## Recent Handovers
|
|
5054
|
+
|
|
5055
|
+
${handoverText}` : "",
|
|
5056
|
+
recapText ? `## Recap
|
|
5057
|
+
|
|
5058
|
+
${recapText}` : "",
|
|
5059
|
+
rulesText ? `## Development Rules
|
|
5060
|
+
|
|
5061
|
+
${rulesText}` : "",
|
|
5062
|
+
strategiesText ? `## Work Strategies
|
|
5063
|
+
|
|
5064
|
+
${strategiesText}` : ""
|
|
5065
|
+
].filter(Boolean);
|
|
5066
|
+
const digest = digestParts.join("\n\n---\n\n");
|
|
5067
|
+
try {
|
|
5068
|
+
writeFileSync2(join7(dir, "context-digest.md"), digest, "utf-8");
|
|
5069
|
+
} catch {
|
|
5070
|
+
}
|
|
5071
|
+
const nextResult = nextTickets(projectState, 5);
|
|
5072
|
+
let candidatesText = "";
|
|
5073
|
+
if (nextResult.kind === "found") {
|
|
5074
|
+
candidatesText = nextResult.candidates.map(
|
|
5075
|
+
(c, i) => `${i + 1}. **${c.ticket.id}: ${c.ticket.title}** (${c.ticket.type}, phase: ${c.ticket.phase ?? "unphased"})${c.unblockImpact.wouldUnblock.length > 0 ? ` \u2014 unblocks ${c.unblockImpact.wouldUnblock.map((t) => t.id).join(", ")}` : ""}`
|
|
5076
|
+
).join("\n");
|
|
5077
|
+
} else if (nextResult.kind === "all_complete") {
|
|
5078
|
+
candidatesText = "All tickets are complete. No work to do.";
|
|
5079
|
+
} else if (nextResult.kind === "all_blocked") {
|
|
5080
|
+
candidatesText = "All remaining tickets are blocked.";
|
|
5081
|
+
} else {
|
|
5082
|
+
candidatesText = "No tickets found.";
|
|
5083
|
+
}
|
|
5084
|
+
const recResult = recommend(projectState, 5);
|
|
5085
|
+
let recsText = "";
|
|
5086
|
+
if (recResult.recommendations.length > 0) {
|
|
5087
|
+
const ticketRecs = recResult.recommendations.filter((r) => r.kind === "ticket");
|
|
5088
|
+
if (ticketRecs.length > 0) {
|
|
5089
|
+
recsText = "\n\n**Recommended:**\n" + ticketRecs.map(
|
|
5090
|
+
(r) => `- ${r.id}: ${r.title} (${r.reason})`
|
|
5091
|
+
).join("\n");
|
|
5092
|
+
}
|
|
5093
|
+
}
|
|
5094
|
+
updated = refreshLease(updated);
|
|
5095
|
+
const pressure = evaluatePressure(updated);
|
|
5096
|
+
updated = { ...updated, contextPressure: { ...updated.contextPressure, level: pressure } };
|
|
5097
|
+
const written = writeSessionSync(dir, updated);
|
|
5098
|
+
appendEvent(dir, {
|
|
5099
|
+
rev: written.revision,
|
|
5100
|
+
type: "start",
|
|
5101
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5102
|
+
data: { recipe, branch: written.git.branch, head: written.git.initHead }
|
|
5103
|
+
});
|
|
5104
|
+
const topCandidate = nextResult.kind === "found" ? nextResult.candidates[0] : null;
|
|
5105
|
+
const instruction = [
|
|
5106
|
+
"# Autonomous Session Started",
|
|
5107
|
+
"",
|
|
5108
|
+
"You are now in autonomous mode. Work continuously until all tickets are done or the session limit is reached.",
|
|
5109
|
+
"Do NOT stop to summarize. Do NOT ask the user. Pick a ticket and start working immediately.",
|
|
5110
|
+
"",
|
|
5111
|
+
"## Ticket Candidates",
|
|
5112
|
+
"",
|
|
5113
|
+
candidatesText,
|
|
5114
|
+
recsText,
|
|
5115
|
+
"",
|
|
5116
|
+
topCandidate ? `Pick **${topCandidate.ticket.id}** (highest priority) by calling \`claudestory_autonomous_guide\` now:` : "Pick a ticket by calling `claudestory_autonomous_guide` now:",
|
|
5117
|
+
"```json",
|
|
5118
|
+
topCandidate ? `{ "sessionId": "${updated.sessionId}", "action": "report", "report": { "completedAction": "ticket_picked", "ticketId": "${topCandidate.ticket.id}" } }` : `{ "sessionId": "${updated.sessionId}", "action": "report", "report": { "completedAction": "ticket_picked", "ticketId": "T-XXX" } }`,
|
|
5119
|
+
"```"
|
|
5120
|
+
].join("\n");
|
|
5121
|
+
return guideResult(updated, "PICK_TICKET", {
|
|
5122
|
+
instruction,
|
|
5123
|
+
reminders: [
|
|
5124
|
+
"Do NOT use Claude Code's plan mode \u2014 write plans as markdown files.",
|
|
5125
|
+
"Do NOT ask the user for confirmation or approval.",
|
|
5126
|
+
"Do NOT stop or summarize between tickets \u2014 call autonomous_guide IMMEDIATELY.",
|
|
5127
|
+
"You are in autonomous mode \u2014 continue working until done."
|
|
5128
|
+
],
|
|
5129
|
+
transitionedFrom: "INIT"
|
|
5130
|
+
});
|
|
5131
|
+
} catch (err) {
|
|
5132
|
+
deleteSession(root, session.sessionId);
|
|
5133
|
+
throw err;
|
|
5134
|
+
}
|
|
5135
|
+
}
|
|
5136
|
+
async function handleReport(root, args) {
|
|
5137
|
+
if (!args.sessionId) return guideError(new Error("sessionId is required for report action"));
|
|
5138
|
+
if (!args.report) return guideError(new Error("report field is required for report action"));
|
|
5139
|
+
const info = findSessionById(root, args.sessionId);
|
|
5140
|
+
if (!info) return guideError(new Error(`Session ${args.sessionId} not found`));
|
|
5141
|
+
let state = refreshLease(info.state);
|
|
5142
|
+
const currentState = state.state;
|
|
5143
|
+
const report = args.report;
|
|
5144
|
+
switch (currentState) {
|
|
5145
|
+
case "PICK_TICKET":
|
|
5146
|
+
return handleReportPickTicket(root, info.dir, state, report);
|
|
5147
|
+
case "PLAN":
|
|
5148
|
+
return handleReportPlan(root, info.dir, state, report);
|
|
5149
|
+
case "PLAN_REVIEW":
|
|
5150
|
+
return handleReportPlanReview(root, info.dir, state, report);
|
|
5151
|
+
case "IMPLEMENT":
|
|
5152
|
+
return handleReportImplement(root, info.dir, state, report);
|
|
5153
|
+
case "CODE_REVIEW":
|
|
5154
|
+
return handleReportCodeReview(root, info.dir, state, report);
|
|
5155
|
+
case "FINALIZE":
|
|
5156
|
+
return handleReportFinalize(root, info.dir, state, report);
|
|
5157
|
+
case "COMPLETE":
|
|
5158
|
+
return handleReportComplete(root, info.dir, state, report);
|
|
5159
|
+
case "HANDOVER":
|
|
5160
|
+
return handleReportHandover(root, info.dir, state, report);
|
|
5161
|
+
default:
|
|
5162
|
+
return guideError(new Error(`Cannot report at state ${currentState}`));
|
|
5163
|
+
}
|
|
5164
|
+
}
|
|
5165
|
+
async function handleReportPickTicket(root, dir, state, report) {
|
|
5166
|
+
const ticketId = report.ticketId;
|
|
5167
|
+
if (!ticketId) return guideError(new Error("report.ticketId is required when picking a ticket"));
|
|
5168
|
+
const { state: projectState } = await loadProject(root);
|
|
5169
|
+
const ticket = projectState.ticketByID(ticketId);
|
|
5170
|
+
if (!ticket) return guideError(new Error(`Ticket ${ticketId} not found`));
|
|
5171
|
+
if (projectState.isBlocked(ticket)) return guideError(new Error(`Ticket ${ticketId} is blocked`));
|
|
5172
|
+
const written = writeSessionSync(dir, {
|
|
5173
|
+
...state,
|
|
5174
|
+
state: "PLAN",
|
|
5175
|
+
previousState: "PICK_TICKET",
|
|
5176
|
+
ticket: { id: ticket.id, title: ticket.title, claimed: true }
|
|
5177
|
+
});
|
|
5178
|
+
appendEvent(dir, {
|
|
5179
|
+
rev: written.revision,
|
|
5180
|
+
type: "ticket_picked",
|
|
5181
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5182
|
+
data: { ticketId: ticket.id, title: ticket.title }
|
|
5183
|
+
});
|
|
5184
|
+
return guideResult(written, "PLAN", {
|
|
5185
|
+
instruction: [
|
|
5186
|
+
`# Plan for ${ticket.id}: ${ticket.title}`,
|
|
5187
|
+
"",
|
|
5188
|
+
ticket.description ? `## Ticket Description
|
|
5189
|
+
|
|
5190
|
+
${ticket.description}` : "",
|
|
5191
|
+
"",
|
|
5192
|
+
`Write an implementation plan for this ticket. Save it to \`.story/sessions/${state.sessionId}/plan.md\`.`,
|
|
5193
|
+
"",
|
|
5194
|
+
"When done, call `claudestory_autonomous_guide` with:",
|
|
5195
|
+
"```json",
|
|
5196
|
+
`{ "sessionId": "${state.sessionId}", "action": "report", "report": { "completedAction": "plan_written" } }`,
|
|
5197
|
+
"```"
|
|
5198
|
+
].join("\n"),
|
|
5199
|
+
reminders: [
|
|
5200
|
+
"Write the plan as a markdown file \u2014 do NOT use Claude Code's plan mode.",
|
|
5201
|
+
"Do NOT ask the user for approval."
|
|
5202
|
+
],
|
|
5203
|
+
transitionedFrom: "PICK_TICKET"
|
|
5204
|
+
});
|
|
5205
|
+
}
|
|
5206
|
+
async function handleReportPlan(root, dir, state, report) {
|
|
5207
|
+
const planPath = join7(dir, "plan.md");
|
|
5208
|
+
if (!existsSync7(planPath)) {
|
|
5209
|
+
return guideResult(state, "PLAN", {
|
|
5210
|
+
instruction: `Plan file not found at ${planPath}. Write your plan there and call me again.`,
|
|
5211
|
+
reminders: ["Save plan to .story/sessions/<id>/plan.md"]
|
|
5212
|
+
});
|
|
5213
|
+
}
|
|
5214
|
+
const planContent = readFileSafe(planPath);
|
|
5215
|
+
if (!planContent || planContent.trim().length === 0) {
|
|
5216
|
+
return guideResult(state, "PLAN", {
|
|
5217
|
+
instruction: "Plan file is empty. Write your implementation plan and call me again.",
|
|
5218
|
+
reminders: []
|
|
5219
|
+
});
|
|
5220
|
+
}
|
|
5221
|
+
const risk = assessRisk(void 0, void 0);
|
|
5222
|
+
const written = writeSessionSync(dir, {
|
|
5223
|
+
...state,
|
|
5224
|
+
state: "PLAN_REVIEW",
|
|
5225
|
+
previousState: "PLAN",
|
|
5226
|
+
ticket: state.ticket ? { ...state.ticket, risk } : state.ticket
|
|
5227
|
+
});
|
|
5228
|
+
appendEvent(dir, {
|
|
5229
|
+
rev: written.revision,
|
|
5230
|
+
type: "plan_written",
|
|
5231
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5232
|
+
data: { planLength: planContent.length, risk }
|
|
5233
|
+
});
|
|
5234
|
+
const backends = state.config.reviewBackends;
|
|
5235
|
+
const reviewer = nextReviewer([], backends);
|
|
5236
|
+
const rounds = requiredRounds(risk);
|
|
5237
|
+
return guideResult(written, "PLAN_REVIEW", {
|
|
5238
|
+
instruction: [
|
|
5239
|
+
`# Plan Review \u2014 Round 1 of ${rounds} minimum`,
|
|
5240
|
+
"",
|
|
5241
|
+
`Run a plan review using **${reviewer}**.`,
|
|
5242
|
+
"",
|
|
5243
|
+
reviewer === "codex" ? `Call \`review_plan\` MCP tool with the plan content.` : `Launch a code review agent to review the plan.`,
|
|
5244
|
+
"",
|
|
5245
|
+
"When done, call `claudestory_autonomous_guide` with:",
|
|
5246
|
+
"```json",
|
|
5247
|
+
`{ "sessionId": "${state.sessionId}", "action": "report", "report": { "completedAction": "plan_review_round", "verdict": "<approve|revise|reject>", "findings": [...] } }`,
|
|
5248
|
+
"```"
|
|
5249
|
+
].join("\n"),
|
|
5250
|
+
reminders: ["Report the exact verdict and findings from the reviewer."],
|
|
5251
|
+
transitionedFrom: "PLAN"
|
|
5252
|
+
});
|
|
5253
|
+
}
|
|
5254
|
+
async function handleReportPlanReview(root, dir, state, report) {
|
|
5255
|
+
const verdict = report.verdict;
|
|
5256
|
+
if (!verdict || !["approve", "revise", "request_changes", "reject"].includes(verdict)) {
|
|
5257
|
+
return guideResult(state, "PLAN_REVIEW", {
|
|
5258
|
+
instruction: 'Invalid verdict. Re-submit with verdict: "approve", "revise", "request_changes", or "reject".',
|
|
5259
|
+
reminders: []
|
|
5260
|
+
});
|
|
5261
|
+
}
|
|
5262
|
+
const planReviews = [...state.reviews.plan];
|
|
5263
|
+
const roundNum = planReviews.length + 1;
|
|
5264
|
+
const findings = report.findings ?? [];
|
|
5265
|
+
const backends = state.config.reviewBackends;
|
|
5266
|
+
const reviewerBackend = nextReviewer(planReviews, backends);
|
|
5267
|
+
planReviews.push({
|
|
5268
|
+
round: roundNum,
|
|
5269
|
+
reviewer: reviewerBackend,
|
|
5270
|
+
verdict,
|
|
5271
|
+
findingCount: findings.length,
|
|
5272
|
+
criticalCount: findings.filter((f) => f.severity === "critical").length,
|
|
5273
|
+
majorCount: findings.filter((f) => f.severity === "major").length,
|
|
5274
|
+
suggestionCount: findings.filter((f) => f.severity === "suggestion").length,
|
|
5275
|
+
codexSessionId: report.reviewerSessionId,
|
|
5276
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
5277
|
+
});
|
|
5278
|
+
const risk = state.ticket?.risk ?? "low";
|
|
5279
|
+
const minRounds = requiredRounds(risk);
|
|
5280
|
+
const hasCriticalOrMajor = findings.some(
|
|
5281
|
+
(f) => f.severity === "critical" || f.severity === "major"
|
|
5282
|
+
);
|
|
5283
|
+
let nextState;
|
|
5284
|
+
if (verdict === "reject") {
|
|
5285
|
+
nextState = "PLAN";
|
|
5286
|
+
} else if (verdict === "approve" || !hasCriticalOrMajor && roundNum >= minRounds) {
|
|
5287
|
+
nextState = "IMPLEMENT";
|
|
5288
|
+
} else if (roundNum >= 5) {
|
|
5289
|
+
nextState = "IMPLEMENT";
|
|
5290
|
+
} else {
|
|
5291
|
+
nextState = "PLAN_REVIEW";
|
|
5292
|
+
}
|
|
5293
|
+
const written = writeSessionSync(dir, {
|
|
5294
|
+
...state,
|
|
5295
|
+
state: nextState,
|
|
5296
|
+
previousState: "PLAN_REVIEW",
|
|
5297
|
+
reviews: { ...state.reviews, plan: planReviews }
|
|
5298
|
+
});
|
|
5299
|
+
appendEvent(dir, {
|
|
5300
|
+
rev: written.revision,
|
|
5301
|
+
type: "plan_review",
|
|
5302
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5303
|
+
data: { round: roundNum, verdict, findingCount: findings.length }
|
|
5304
|
+
});
|
|
5305
|
+
if (nextState === "PLAN") {
|
|
5306
|
+
return guideResult(written, "PLAN", {
|
|
5307
|
+
instruction: 'Plan was rejected. Revise and rewrite the plan, then call me with completedAction: "plan_written".',
|
|
5308
|
+
reminders: [],
|
|
5309
|
+
transitionedFrom: "PLAN_REVIEW"
|
|
5310
|
+
});
|
|
5311
|
+
}
|
|
5312
|
+
if (nextState === "IMPLEMENT") {
|
|
5313
|
+
return guideResult(written, "IMPLEMENT", {
|
|
5314
|
+
instruction: [
|
|
5315
|
+
"# Implement",
|
|
5316
|
+
"",
|
|
5317
|
+
"Plan review passed. Implement the plan now.",
|
|
5318
|
+
"",
|
|
5319
|
+
"When done, call `claudestory_autonomous_guide` with:",
|
|
5320
|
+
"```json",
|
|
5321
|
+
`{ "sessionId": "${state.sessionId}", "action": "report", "report": { "completedAction": "implementation_done" } }`,
|
|
5322
|
+
"```"
|
|
5323
|
+
].join("\n"),
|
|
5324
|
+
reminders: ["Call autonomous_guide when implementation is complete."],
|
|
5325
|
+
transitionedFrom: "PLAN_REVIEW"
|
|
5326
|
+
});
|
|
5327
|
+
}
|
|
5328
|
+
const reviewer = nextReviewer(planReviews, backends);
|
|
5329
|
+
return guideResult(written, "PLAN_REVIEW", {
|
|
5330
|
+
instruction: [
|
|
5331
|
+
`# Plan Review \u2014 Round ${roundNum + 1}`,
|
|
5332
|
+
"",
|
|
5333
|
+
hasCriticalOrMajor ? `Round ${roundNum} found ${findings.filter((f) => f.severity === "critical" || f.severity === "major").length} critical/major finding(s). Address them, then re-review with **${reviewer}**.` : `Round ${roundNum} complete. Run round ${roundNum + 1} with **${reviewer}**.`,
|
|
5334
|
+
"",
|
|
5335
|
+
"Report verdict and findings as before."
|
|
5336
|
+
].join("\n"),
|
|
5337
|
+
reminders: ["Address findings before re-reviewing."]
|
|
5338
|
+
});
|
|
5339
|
+
}
|
|
5340
|
+
async function handleReportImplement(root, dir, state, report) {
|
|
5341
|
+
let realizedRisk = state.ticket?.risk ?? "low";
|
|
5342
|
+
const mergeBase = state.git.mergeBase;
|
|
5343
|
+
if (mergeBase) {
|
|
5344
|
+
const diffResult = await gitDiffStat(root, mergeBase);
|
|
5345
|
+
const namesResult = await gitDiffNames(root, mergeBase);
|
|
5346
|
+
if (diffResult.ok) {
|
|
5347
|
+
realizedRisk = assessRisk(diffResult.data, namesResult.ok ? namesResult.data : void 0);
|
|
5348
|
+
}
|
|
5349
|
+
}
|
|
5350
|
+
const backends = state.config.reviewBackends;
|
|
5351
|
+
const codeReviews = state.reviews.code;
|
|
5352
|
+
const reviewer = nextReviewer(codeReviews, backends);
|
|
5353
|
+
const rounds = requiredRounds(realizedRisk);
|
|
5354
|
+
const written = writeSessionSync(dir, {
|
|
5355
|
+
...state,
|
|
5356
|
+
state: "CODE_REVIEW",
|
|
5357
|
+
previousState: "IMPLEMENT",
|
|
5358
|
+
ticket: state.ticket ? { ...state.ticket, realizedRisk } : state.ticket
|
|
5359
|
+
});
|
|
5360
|
+
appendEvent(dir, {
|
|
5361
|
+
rev: written.revision,
|
|
5362
|
+
type: "implementation_done",
|
|
5363
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5364
|
+
data: { realizedRisk }
|
|
5365
|
+
});
|
|
5366
|
+
return guideResult(written, "CODE_REVIEW", {
|
|
5367
|
+
instruction: [
|
|
5368
|
+
`# Code Review \u2014 Round 1 of ${rounds} minimum`,
|
|
5369
|
+
"",
|
|
5370
|
+
`Realized risk: **${realizedRisk}**${realizedRisk !== state.ticket?.risk ? ` (was ${state.ticket?.risk})` : ""}.`,
|
|
5371
|
+
"",
|
|
5372
|
+
`Run a code review using **${reviewer}**. Capture the git diff and pass it to the reviewer.`,
|
|
5373
|
+
"",
|
|
5374
|
+
"When done, report verdict and findings."
|
|
5375
|
+
].join("\n"),
|
|
5376
|
+
reminders: ["Capture diff with `git diff` and pass to reviewer."],
|
|
5377
|
+
transitionedFrom: "IMPLEMENT"
|
|
5378
|
+
});
|
|
5379
|
+
}
|
|
5380
|
+
async function handleReportCodeReview(root, dir, state, report) {
|
|
5381
|
+
const verdict = report.verdict;
|
|
5382
|
+
if (!verdict || !["approve", "revise", "request_changes", "reject"].includes(verdict)) {
|
|
5383
|
+
return guideResult(state, "CODE_REVIEW", {
|
|
5384
|
+
instruction: 'Invalid verdict. Re-submit with verdict: "approve", "revise", "request_changes", or "reject".',
|
|
5385
|
+
reminders: []
|
|
5386
|
+
});
|
|
5387
|
+
}
|
|
5388
|
+
const codeReviews = [...state.reviews.code];
|
|
5389
|
+
const roundNum = codeReviews.length + 1;
|
|
5390
|
+
const findings = report.findings ?? [];
|
|
5391
|
+
const backends = state.config.reviewBackends;
|
|
5392
|
+
const reviewerBackend = nextReviewer(codeReviews, backends);
|
|
5393
|
+
codeReviews.push({
|
|
5394
|
+
round: roundNum,
|
|
5395
|
+
reviewer: reviewerBackend,
|
|
5396
|
+
verdict,
|
|
5397
|
+
findingCount: findings.length,
|
|
5398
|
+
criticalCount: findings.filter((f) => f.severity === "critical").length,
|
|
5399
|
+
majorCount: findings.filter((f) => f.severity === "major").length,
|
|
5400
|
+
suggestionCount: findings.filter((f) => f.severity === "suggestion").length,
|
|
5401
|
+
codexSessionId: report.reviewerSessionId,
|
|
5402
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
5403
|
+
});
|
|
5404
|
+
const risk = state.ticket?.realizedRisk ?? state.ticket?.risk ?? "low";
|
|
5405
|
+
const minRounds = requiredRounds(risk);
|
|
5406
|
+
const hasCriticalOrMajor = findings.some(
|
|
5407
|
+
(f) => f.severity === "critical" || f.severity === "major"
|
|
5408
|
+
);
|
|
5409
|
+
const planRedirect = findings.some((f) => f.recommendedNextState === "PLAN");
|
|
5410
|
+
let nextState;
|
|
5411
|
+
if (verdict === "reject" && planRedirect) {
|
|
5412
|
+
nextState = "PLAN";
|
|
5413
|
+
} else if (verdict === "reject") {
|
|
5414
|
+
nextState = "IMPLEMENT";
|
|
5415
|
+
} else if (verdict === "approve" || !hasCriticalOrMajor && roundNum >= minRounds) {
|
|
5416
|
+
nextState = "FINALIZE";
|
|
5417
|
+
} else if (roundNum >= 5) {
|
|
5418
|
+
nextState = "FINALIZE";
|
|
5419
|
+
} else {
|
|
5420
|
+
nextState = "CODE_REVIEW";
|
|
5421
|
+
}
|
|
5422
|
+
const written = writeSessionSync(dir, {
|
|
5423
|
+
...state,
|
|
5424
|
+
state: nextState,
|
|
5425
|
+
previousState: "CODE_REVIEW",
|
|
5426
|
+
reviews: { ...state.reviews, code: codeReviews }
|
|
5427
|
+
});
|
|
5428
|
+
appendEvent(dir, {
|
|
5429
|
+
rev: written.revision,
|
|
5430
|
+
type: "code_review",
|
|
5431
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5432
|
+
data: { round: roundNum, verdict, findingCount: findings.length }
|
|
5433
|
+
});
|
|
5434
|
+
if (nextState === "PLAN") {
|
|
5435
|
+
return guideResult(written, "PLAN", {
|
|
5436
|
+
instruction: 'Code review recommends rethinking the approach. Revise the plan and call me with completedAction: "plan_written".',
|
|
5437
|
+
reminders: [],
|
|
5438
|
+
transitionedFrom: "CODE_REVIEW"
|
|
5439
|
+
});
|
|
5440
|
+
}
|
|
5441
|
+
if (nextState === "IMPLEMENT") {
|
|
5442
|
+
return guideResult(written, "IMPLEMENT", {
|
|
5443
|
+
instruction: 'Code review requested changes. Fix the issues and call me with completedAction: "implementation_done".',
|
|
5444
|
+
reminders: ["Address all critical/major findings before re-submitting."],
|
|
5445
|
+
transitionedFrom: "CODE_REVIEW"
|
|
5446
|
+
});
|
|
5447
|
+
}
|
|
5448
|
+
if (nextState === "FINALIZE") {
|
|
5449
|
+
return guideResult(written, "FINALIZE", {
|
|
5450
|
+
instruction: [
|
|
5451
|
+
"# Finalize",
|
|
5452
|
+
"",
|
|
5453
|
+
"Code review passed. Time to commit.",
|
|
5454
|
+
"",
|
|
5455
|
+
state.ticket ? `1. Update ticket ${state.ticket.id} status to "complete" in .story/` : "",
|
|
5456
|
+
"2. Stage all changed files (code + .story/ changes)",
|
|
5457
|
+
'3. Call me with completedAction: "files_staged"'
|
|
5458
|
+
].filter(Boolean).join("\n"),
|
|
5459
|
+
reminders: ["Stage both code changes and .story/ ticket update in the same commit."],
|
|
5460
|
+
transitionedFrom: "CODE_REVIEW"
|
|
5461
|
+
});
|
|
5462
|
+
}
|
|
5463
|
+
const reviewer = nextReviewer(codeReviews, backends);
|
|
5464
|
+
return guideResult(written, "CODE_REVIEW", {
|
|
5465
|
+
instruction: `Code review round ${roundNum} found issues. Fix them and re-review with **${reviewer}**.`,
|
|
5466
|
+
reminders: []
|
|
5467
|
+
});
|
|
5468
|
+
}
|
|
5469
|
+
async function handleReportFinalize(root, dir, state, report) {
|
|
5470
|
+
const action = report.completedAction;
|
|
5471
|
+
const checkpoint = state.finalizeCheckpoint;
|
|
5472
|
+
if (action === "files_staged" && (!checkpoint || checkpoint === "staged")) {
|
|
5473
|
+
const stagedResult = await gitDiffCachedNames(root);
|
|
5474
|
+
if (!stagedResult.ok || stagedResult.data.length === 0) {
|
|
5475
|
+
return guideResult(state, "FINALIZE", {
|
|
5476
|
+
instruction: 'No files are staged. Stage your changes and call me again with completedAction: "files_staged".',
|
|
5477
|
+
reminders: []
|
|
5478
|
+
});
|
|
5479
|
+
}
|
|
5480
|
+
const written = writeSessionSync(dir, { ...state, finalizeCheckpoint: "staged" });
|
|
5481
|
+
return guideResult(written, "FINALIZE", {
|
|
5482
|
+
instruction: [
|
|
5483
|
+
"Files staged. Now run pre-commit checks.",
|
|
5484
|
+
"",
|
|
5485
|
+
'Run any pre-commit hooks or linting, then call me with completedAction: "precommit_passed".',
|
|
5486
|
+
'If pre-commit fails, fix the issues, re-stage, and call me with completedAction: "files_staged" again.'
|
|
5487
|
+
].join("\n"),
|
|
5488
|
+
reminders: ["Verify staged set is intact after pre-commit hooks."]
|
|
5489
|
+
});
|
|
5490
|
+
}
|
|
5491
|
+
if (action === "precommit_passed") {
|
|
5492
|
+
if (!checkpoint || checkpoint === null) {
|
|
5493
|
+
return guideResult(state, "FINALIZE", {
|
|
5494
|
+
instruction: 'You must stage files first. Call me with completedAction: "files_staged" after staging.',
|
|
5495
|
+
reminders: []
|
|
5496
|
+
});
|
|
5497
|
+
}
|
|
5498
|
+
const stagedResult = await gitDiffCachedNames(root);
|
|
5499
|
+
if (!stagedResult.ok || stagedResult.data.length === 0) {
|
|
5500
|
+
const written2 = writeSessionSync(dir, { ...state, finalizeCheckpoint: null });
|
|
5501
|
+
return guideResult(written2, "FINALIZE", {
|
|
5502
|
+
instruction: 'Pre-commit hooks appear to have cleared the staging area. Re-stage your changes and call me with completedAction: "files_staged".',
|
|
5503
|
+
reminders: []
|
|
5504
|
+
});
|
|
5505
|
+
}
|
|
5506
|
+
const written = writeSessionSync(dir, { ...state, finalizeCheckpoint: "precommit_passed" });
|
|
5507
|
+
return guideResult(written, "FINALIZE", {
|
|
5508
|
+
instruction: [
|
|
5509
|
+
"Pre-commit passed. Now commit.",
|
|
5510
|
+
"",
|
|
5511
|
+
state.ticket ? `Commit with message: "feat: <description> (${state.ticket.id})"` : "Commit with a descriptive message.",
|
|
5512
|
+
"",
|
|
5513
|
+
'Call me with completedAction: "commit_done" and include the commitHash.'
|
|
5514
|
+
].join("\n"),
|
|
5515
|
+
reminders: []
|
|
5516
|
+
});
|
|
5517
|
+
}
|
|
5518
|
+
if (action === "commit_done") {
|
|
5519
|
+
if (!checkpoint || checkpoint === null) {
|
|
5520
|
+
return guideResult(state, "FINALIZE", {
|
|
5521
|
+
instruction: 'You must stage files first. Call me with completedAction: "files_staged" after staging.',
|
|
5522
|
+
reminders: []
|
|
5523
|
+
});
|
|
5524
|
+
}
|
|
5525
|
+
if (checkpoint === "staged") {
|
|
5526
|
+
return guideResult(state, "FINALIZE", {
|
|
5527
|
+
instruction: 'You must pass pre-commit checks first. Call me with completedAction: "precommit_passed".',
|
|
5528
|
+
reminders: []
|
|
5529
|
+
});
|
|
5530
|
+
}
|
|
5531
|
+
if (checkpoint === "committed") {
|
|
5532
|
+
return guideResult(state, "FINALIZE", {
|
|
5533
|
+
instruction: "Commit was already recorded. Proceeding to completion.",
|
|
5534
|
+
reminders: []
|
|
5535
|
+
});
|
|
5536
|
+
}
|
|
5537
|
+
const commitHash = report.commitHash;
|
|
5538
|
+
if (!commitHash) {
|
|
5539
|
+
return guideResult(state, "FINALIZE", {
|
|
5540
|
+
instruction: "Missing commitHash in report. Call me again with the commit hash.",
|
|
5541
|
+
reminders: []
|
|
5542
|
+
});
|
|
5543
|
+
}
|
|
5544
|
+
const completedTicket = state.ticket ? { id: state.ticket.id, title: state.ticket.title, commitHash, risk: state.ticket.risk } : void 0;
|
|
5545
|
+
const updated = {
|
|
5546
|
+
...state,
|
|
5547
|
+
state: "COMPLETE",
|
|
5548
|
+
previousState: "FINALIZE",
|
|
5549
|
+
finalizeCheckpoint: "committed",
|
|
5550
|
+
completedTickets: completedTicket ? [...state.completedTickets, completedTicket] : state.completedTickets,
|
|
5551
|
+
ticket: void 0
|
|
5552
|
+
};
|
|
5553
|
+
const written = writeSessionSync(dir, updated);
|
|
5554
|
+
appendEvent(dir, {
|
|
5555
|
+
rev: written.revision,
|
|
5556
|
+
type: "commit",
|
|
5557
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5558
|
+
data: { commitHash, ticketId: completedTicket?.id }
|
|
5559
|
+
});
|
|
5560
|
+
return handleReportComplete(root, dir, refreshLease(written), { completedAction: "commit_done" });
|
|
5561
|
+
}
|
|
5562
|
+
return guideResult(state, "FINALIZE", {
|
|
5563
|
+
instruction: 'Unexpected action at FINALIZE. Stage files and call with completedAction: "files_staged", or commit and call with completedAction: "commit_done".',
|
|
5564
|
+
reminders: []
|
|
5565
|
+
});
|
|
5566
|
+
}
|
|
5567
|
+
async function handleReportComplete(root, dir, state, report) {
|
|
5568
|
+
const pressure = evaluatePressure(state);
|
|
5569
|
+
const updated = {
|
|
5570
|
+
...state,
|
|
5571
|
+
state: "COMPLETE",
|
|
5572
|
+
contextPressure: { ...state.contextPressure, level: pressure },
|
|
5573
|
+
finalizeCheckpoint: null
|
|
5574
|
+
};
|
|
5575
|
+
const ticketsDone = updated.completedTickets.length;
|
|
5576
|
+
const maxTickets = updated.config.maxTicketsPerSession;
|
|
5577
|
+
let nextState;
|
|
5578
|
+
let advice = "ok";
|
|
5579
|
+
if (pressure === "critical") {
|
|
5580
|
+
nextState = "HANDOVER";
|
|
5581
|
+
advice = "compact-now";
|
|
5582
|
+
} else if (ticketsDone >= maxTickets) {
|
|
5583
|
+
nextState = "HANDOVER";
|
|
5584
|
+
} else if (pressure === "high") {
|
|
5585
|
+
advice = "consider-compact";
|
|
5586
|
+
nextState = "PICK_TICKET";
|
|
5587
|
+
} else {
|
|
5588
|
+
nextState = "PICK_TICKET";
|
|
5589
|
+
}
|
|
5590
|
+
const { state: projectState } = await loadProject(root);
|
|
5591
|
+
const nextResult = nextTickets(projectState, 1);
|
|
5592
|
+
if (nextResult.kind !== "found") {
|
|
5593
|
+
nextState = "HANDOVER";
|
|
5594
|
+
}
|
|
5595
|
+
const transitioned = writeSessionSync(dir, {
|
|
5596
|
+
...updated,
|
|
5597
|
+
state: nextState,
|
|
5598
|
+
previousState: "COMPLETE"
|
|
5599
|
+
});
|
|
5600
|
+
if (nextState === "HANDOVER") {
|
|
5601
|
+
return guideResult(transitioned, "HANDOVER", {
|
|
5602
|
+
instruction: [
|
|
5603
|
+
`# Session Complete \u2014 ${ticketsDone} ticket(s) done`,
|
|
5604
|
+
"",
|
|
5605
|
+
"Write a session handover summarizing what was accomplished, decisions made, and what's next.",
|
|
5606
|
+
"",
|
|
5607
|
+
'Call me with completedAction: "handover_written" and include the content in handoverContent.'
|
|
5608
|
+
].join("\n"),
|
|
5609
|
+
reminders: [],
|
|
5610
|
+
transitionedFrom: "COMPLETE",
|
|
5611
|
+
contextAdvice: advice
|
|
5612
|
+
});
|
|
5613
|
+
}
|
|
5614
|
+
const candidates = nextTickets(projectState, 5);
|
|
5615
|
+
let candidatesText = "";
|
|
5616
|
+
if (candidates.kind === "found") {
|
|
5617
|
+
candidatesText = candidates.candidates.map(
|
|
5618
|
+
(c, i) => `${i + 1}. **${c.ticket.id}: ${c.ticket.title}** (${c.ticket.type})`
|
|
5619
|
+
).join("\n");
|
|
5620
|
+
}
|
|
5621
|
+
const topCandidate = candidates.kind === "found" ? candidates.candidates[0] : null;
|
|
5622
|
+
return guideResult(transitioned, "PICK_TICKET", {
|
|
5623
|
+
instruction: [
|
|
5624
|
+
`# Ticket Complete \u2014 Continuing (${ticketsDone}/${maxTickets})`,
|
|
5625
|
+
"",
|
|
5626
|
+
"Do NOT stop. Do NOT ask the user. Continue immediately with the next ticket.",
|
|
5627
|
+
"",
|
|
5628
|
+
candidatesText,
|
|
5629
|
+
"",
|
|
5630
|
+
topCandidate ? `Pick **${topCandidate.ticket.id}** (highest priority) by calling \`claudestory_autonomous_guide\` now:` : "Pick a ticket by calling `claudestory_autonomous_guide` now:",
|
|
5631
|
+
"```json",
|
|
5632
|
+
topCandidate ? `{ "sessionId": "${transitioned.sessionId}", "action": "report", "report": { "completedAction": "ticket_picked", "ticketId": "${topCandidate.ticket.id}" } }` : `{ "sessionId": "${transitioned.sessionId}", "action": "report", "report": { "completedAction": "ticket_picked", "ticketId": "T-XXX" } }`,
|
|
5633
|
+
"```"
|
|
5634
|
+
].join("\n"),
|
|
5635
|
+
reminders: [
|
|
5636
|
+
"Do NOT stop or summarize. Call autonomous_guide IMMEDIATELY to pick the next ticket.",
|
|
5637
|
+
"Do NOT ask the user for confirmation.",
|
|
5638
|
+
"You are in autonomous mode \u2014 continue working until all tickets are done or the session limit is reached."
|
|
5639
|
+
],
|
|
5640
|
+
transitionedFrom: "COMPLETE",
|
|
5641
|
+
contextAdvice: advice
|
|
5642
|
+
});
|
|
5643
|
+
}
|
|
5644
|
+
async function handleReportHandover(root, dir, state, report) {
|
|
5645
|
+
const content = report.handoverContent;
|
|
5646
|
+
if (!content) {
|
|
5647
|
+
return guideResult(state, "HANDOVER", {
|
|
5648
|
+
instruction: "Missing handoverContent. Write the handover and include it in the report.",
|
|
5649
|
+
reminders: []
|
|
5650
|
+
});
|
|
5651
|
+
}
|
|
5652
|
+
let handoverFailed = false;
|
|
5653
|
+
try {
|
|
5654
|
+
await handleHandoverCreate(content, "auto-session", "md", root);
|
|
5655
|
+
} catch (err) {
|
|
5656
|
+
handoverFailed = true;
|
|
5657
|
+
try {
|
|
5658
|
+
const fallbackPath = join7(dir, "handover-fallback.md");
|
|
5659
|
+
writeFileSync2(fallbackPath, content, "utf-8");
|
|
5660
|
+
} catch {
|
|
5661
|
+
}
|
|
5662
|
+
}
|
|
5663
|
+
const written = writeSessionSync(dir, {
|
|
5664
|
+
...state,
|
|
5665
|
+
state: "SESSION_END",
|
|
5666
|
+
previousState: "HANDOVER",
|
|
5667
|
+
status: "completed"
|
|
5668
|
+
});
|
|
5669
|
+
appendEvent(dir, {
|
|
5670
|
+
rev: written.revision,
|
|
5671
|
+
type: "session_end",
|
|
5672
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5673
|
+
data: { ticketsCompleted: written.completedTickets.length, handoverFailed }
|
|
5674
|
+
});
|
|
5675
|
+
const ticketsDone = written.completedTickets.length;
|
|
5676
|
+
return guideResult(written, "SESSION_END", {
|
|
5677
|
+
instruction: [
|
|
5678
|
+
"# Session Complete",
|
|
5679
|
+
"",
|
|
5680
|
+
`${ticketsDone} ticket(s) completed.${handoverFailed ? " Handover creation failed \u2014 fallback saved to session directory." : " Handover written."} Session ended.`,
|
|
5681
|
+
"",
|
|
5682
|
+
written.completedTickets.map((t) => `- ${t.id}${t.title ? `: ${t.title}` : ""} (${t.commitHash ?? "no commit"})`).join("\n")
|
|
5683
|
+
].join("\n"),
|
|
5684
|
+
reminders: [],
|
|
5685
|
+
transitionedFrom: "HANDOVER"
|
|
5686
|
+
});
|
|
5687
|
+
}
|
|
5688
|
+
async function handleResume(root, args) {
|
|
5689
|
+
if (!args.sessionId) return guideError(new Error("sessionId is required for resume"));
|
|
5690
|
+
const info = findSessionById(root, args.sessionId);
|
|
5691
|
+
if (!info) return guideError(new Error(`Session ${args.sessionId} not found`));
|
|
5692
|
+
if (info.state.state !== "COMPACT") {
|
|
5693
|
+
return guideError(new Error(
|
|
5694
|
+
`Session ${args.sessionId} is not in COMPACT state (current: ${info.state.state}). Use action: "report" to continue.`
|
|
5695
|
+
));
|
|
5696
|
+
}
|
|
5697
|
+
const resumeState = info.state.preCompactState;
|
|
5698
|
+
if (!resumeState || !WORKFLOW_STATES.includes(resumeState)) {
|
|
5699
|
+
return guideError(new Error(
|
|
5700
|
+
`Session ${args.sessionId} has invalid preCompactState: ${resumeState}. Cannot resume safely.`
|
|
5701
|
+
));
|
|
5702
|
+
}
|
|
5703
|
+
const written = writeSessionSync(info.dir, {
|
|
5704
|
+
...refreshLease(info.state),
|
|
5705
|
+
state: resumeState,
|
|
5706
|
+
preCompactState: null,
|
|
5707
|
+
resumeFromRevision: null,
|
|
5708
|
+
contextPressure: { ...info.state.contextPressure, compactionCount: (info.state.contextPressure?.compactionCount ?? 0) + 1 }
|
|
5709
|
+
});
|
|
5710
|
+
return guideResult(written, resumeState, {
|
|
5711
|
+
instruction: [
|
|
5712
|
+
"# Resumed After Compact",
|
|
5713
|
+
"",
|
|
5714
|
+
`Session restored at state: **${resumeState}**.`,
|
|
5715
|
+
written.ticket ? `Working on: **${written.ticket.id}: ${written.ticket.title}**` : "No ticket in progress.",
|
|
5716
|
+
"",
|
|
5717
|
+
"Continue where you left off. Call me when you complete the current step."
|
|
5718
|
+
].join("\n"),
|
|
5719
|
+
reminders: [
|
|
5720
|
+
"Do NOT use plan mode.",
|
|
5721
|
+
"Call autonomous_guide after completing each step."
|
|
5722
|
+
]
|
|
5723
|
+
});
|
|
5724
|
+
}
|
|
5725
|
+
async function handlePreCompact(root, args) {
|
|
5726
|
+
if (!args.sessionId) return guideError(new Error("sessionId is required for pre_compact"));
|
|
5727
|
+
const info = findSessionById(root, args.sessionId);
|
|
5728
|
+
if (!info) return guideError(new Error(`Session ${args.sessionId} not found`));
|
|
5729
|
+
if (info.state.state === "SESSION_END") {
|
|
5730
|
+
return guideError(new Error(`Session ${args.sessionId} is already ended and cannot be compacted.`));
|
|
5731
|
+
}
|
|
5732
|
+
if (info.state.state === "COMPACT") {
|
|
5733
|
+
return guideError(new Error(`Session ${args.sessionId} is already in COMPACT state. Call action: "resume" to continue.`));
|
|
5734
|
+
}
|
|
5735
|
+
const headResult = await gitHead(root);
|
|
5736
|
+
const written = writeSessionSync(info.dir, {
|
|
5737
|
+
...refreshLease(info.state),
|
|
5738
|
+
state: "COMPACT",
|
|
5739
|
+
previousState: info.state.state,
|
|
5740
|
+
preCompactState: info.state.state,
|
|
5741
|
+
resumeFromRevision: info.state.revision,
|
|
5742
|
+
git: {
|
|
5743
|
+
...info.state.git,
|
|
5744
|
+
expectedHead: headResult.ok ? headResult.data.hash : info.state.git.expectedHead
|
|
5745
|
+
}
|
|
5746
|
+
});
|
|
5747
|
+
try {
|
|
5748
|
+
const loadResult = await loadProject(root);
|
|
5749
|
+
const { saveSnapshot: saveSnapshot2 } = await Promise.resolve().then(() => (init_snapshot(), snapshot_exports));
|
|
5750
|
+
await saveSnapshot2(root, loadResult);
|
|
5751
|
+
} catch {
|
|
5752
|
+
}
|
|
5753
|
+
return guideResult(written, "COMPACT", {
|
|
5754
|
+
instruction: [
|
|
5755
|
+
"# Ready for Compact",
|
|
5756
|
+
"",
|
|
5757
|
+
"State flushed. Run `/compact` now.",
|
|
5758
|
+
"",
|
|
5759
|
+
"After compact, call `claudestory_autonomous_guide` with:",
|
|
5760
|
+
"```json",
|
|
5761
|
+
`{ "sessionId": "${written.sessionId}", "action": "resume" }`,
|
|
5762
|
+
"```"
|
|
5763
|
+
].join("\n"),
|
|
5764
|
+
reminders: []
|
|
5765
|
+
});
|
|
5766
|
+
}
|
|
5767
|
+
async function handleCancel(root, args) {
|
|
5768
|
+
if (!args.sessionId) {
|
|
5769
|
+
const active = findActiveSessionFull(root);
|
|
5770
|
+
if (!active) return guideError(new Error("No active session to cancel"));
|
|
5771
|
+
args = { ...args, sessionId: active.state.sessionId };
|
|
5772
|
+
}
|
|
5773
|
+
const info = findSessionById(root, args.sessionId);
|
|
5774
|
+
if (!info) return guideError(new Error(`Session ${args.sessionId} not found`));
|
|
5775
|
+
const written = writeSessionSync(info.dir, {
|
|
5776
|
+
...info.state,
|
|
5777
|
+
state: "SESSION_END",
|
|
5778
|
+
previousState: info.state.state,
|
|
5779
|
+
status: "completed"
|
|
5780
|
+
});
|
|
5781
|
+
appendEvent(info.dir, {
|
|
5782
|
+
rev: written.revision,
|
|
5783
|
+
type: "cancelled",
|
|
5784
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5785
|
+
data: { previousState: info.state.state }
|
|
5786
|
+
});
|
|
5787
|
+
return {
|
|
5788
|
+
content: [{ type: "text", text: `Session ${args.sessionId} cancelled. ${written.completedTickets.length} ticket(s) were completed.` }]
|
|
5789
|
+
};
|
|
5790
|
+
}
|
|
5791
|
+
function guideResult(state, currentState, opts) {
|
|
5792
|
+
const summary = {
|
|
5793
|
+
ticket: state.ticket ? `${state.ticket.id}: ${state.ticket.title}` : "none",
|
|
5794
|
+
risk: state.ticket?.risk ?? "unknown",
|
|
5795
|
+
completed: state.completedTickets.map((t) => t.id),
|
|
5796
|
+
currentStep: currentState,
|
|
5797
|
+
contextPressure: state.contextPressure?.level ?? "low",
|
|
5798
|
+
branch: state.git?.branch ?? null
|
|
5799
|
+
};
|
|
5800
|
+
const output = {
|
|
5801
|
+
sessionId: state.sessionId,
|
|
5802
|
+
state: currentState,
|
|
5803
|
+
transitionedFrom: opts.transitionedFrom,
|
|
5804
|
+
instruction: opts.instruction,
|
|
5805
|
+
reminders: opts.reminders ?? [],
|
|
5806
|
+
contextAdvice: opts.contextAdvice ?? "ok",
|
|
5807
|
+
sessionSummary: summary
|
|
5808
|
+
};
|
|
5809
|
+
const parts = [
|
|
5810
|
+
output.instruction,
|
|
5811
|
+
"",
|
|
5812
|
+
"---",
|
|
5813
|
+
`**Session:** ${output.sessionId}`,
|
|
5814
|
+
`**State:** ${output.state}${output.transitionedFrom ? ` (from ${output.transitionedFrom})` : ""}`,
|
|
5815
|
+
`**Ticket:** ${summary.ticket}`,
|
|
5816
|
+
`**Risk:** ${summary.risk}`,
|
|
5817
|
+
`**Completed:** ${summary.completed.length > 0 ? summary.completed.join(", ") : "none"}`,
|
|
5818
|
+
`**Pressure:** ${summary.contextPressure}`,
|
|
5819
|
+
summary.branch ? `**Branch:** ${summary.branch}` : "",
|
|
5820
|
+
output.contextAdvice !== "ok" ? `**Context:** ${output.contextAdvice}` : "",
|
|
5821
|
+
output.reminders.length > 0 ? `
|
|
5822
|
+
**Reminders:**
|
|
5823
|
+
${output.reminders.map((r) => `- ${r}`).join("\n")}` : ""
|
|
5824
|
+
].filter(Boolean);
|
|
5825
|
+
return { content: [{ type: "text", text: parts.join("\n") }] };
|
|
5826
|
+
}
|
|
5827
|
+
function guideError(err) {
|
|
5828
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
5829
|
+
return {
|
|
5830
|
+
content: [{ type: "text", text: `[autonomous_guide error] ${message}` }],
|
|
5831
|
+
isError: true
|
|
5832
|
+
};
|
|
5833
|
+
}
|
|
5834
|
+
function readFileSafe(path2) {
|
|
5835
|
+
try {
|
|
5836
|
+
return readFileSync2(path2, "utf-8");
|
|
5837
|
+
} catch {
|
|
5838
|
+
return "";
|
|
5839
|
+
}
|
|
5840
|
+
}
|
|
5841
|
+
var workspaceLocks;
|
|
5842
|
+
var init_guide = __esm({
|
|
5843
|
+
"src/autonomous/guide.ts"() {
|
|
5844
|
+
"use strict";
|
|
5845
|
+
init_esm_shims();
|
|
5846
|
+
init_session_types();
|
|
5847
|
+
init_session();
|
|
5848
|
+
init_state_machine();
|
|
5849
|
+
init_context_pressure();
|
|
5850
|
+
init_review_depth();
|
|
5851
|
+
init_git_inspector();
|
|
5852
|
+
init_project_loader();
|
|
5853
|
+
init_snapshot();
|
|
5854
|
+
init_snapshot();
|
|
5855
|
+
init_queries();
|
|
5856
|
+
init_recommend();
|
|
5857
|
+
init_handover();
|
|
5858
|
+
workspaceLocks = /* @__PURE__ */ new Map();
|
|
5859
|
+
}
|
|
5860
|
+
});
|
|
5861
|
+
|
|
4344
5862
|
// src/cli/commands/phase.ts
|
|
4345
|
-
import { join as
|
|
5863
|
+
import { join as join8, resolve as resolve6 } from "path";
|
|
4346
5864
|
function validatePhaseId(id) {
|
|
4347
5865
|
if (id.length > PHASE_ID_MAX_LENGTH) {
|
|
4348
5866
|
throw new CliValidationError("invalid_input", `Phase ID "${id}" exceeds ${PHASE_ID_MAX_LENGTH} characters`);
|
|
@@ -4531,21 +6049,21 @@ async function handlePhaseDelete(id, reassign, format, root) {
|
|
|
4531
6049
|
const updated = { ...ticket, phase: reassign, order: maxOrder };
|
|
4532
6050
|
const parsed = TicketSchema.parse(updated);
|
|
4533
6051
|
const content = serializeJSON(parsed);
|
|
4534
|
-
const target =
|
|
6052
|
+
const target = join8(wrapDir, "tickets", `${parsed.id}.json`);
|
|
4535
6053
|
operations.push({ op: "write", target, content });
|
|
4536
6054
|
}
|
|
4537
6055
|
for (const issue of affectedIssues) {
|
|
4538
6056
|
const updated = { ...issue, phase: reassign };
|
|
4539
6057
|
const parsed = IssueSchema.parse(updated);
|
|
4540
6058
|
const content = serializeJSON(parsed);
|
|
4541
|
-
const target =
|
|
6059
|
+
const target = join8(wrapDir, "issues", `${parsed.id}.json`);
|
|
4542
6060
|
operations.push({ op: "write", target, content });
|
|
4543
6061
|
}
|
|
4544
6062
|
const newPhases = state.roadmap.phases.filter((p) => p.id !== id);
|
|
4545
6063
|
const newRoadmap = { ...state.roadmap, phases: newPhases };
|
|
4546
6064
|
const parsedRoadmap = RoadmapSchema.parse(newRoadmap);
|
|
4547
6065
|
const roadmapContent = serializeJSON(parsedRoadmap);
|
|
4548
|
-
const roadmapTarget =
|
|
6066
|
+
const roadmapTarget = join8(wrapDir, "roadmap.json");
|
|
4549
6067
|
operations.push({ op: "write", target: roadmapTarget, content: roadmapContent });
|
|
4550
6068
|
await runTransactionUnlocked(root, operations);
|
|
4551
6069
|
} else {
|
|
@@ -4577,15 +6095,15 @@ var init_phase = __esm({
|
|
|
4577
6095
|
});
|
|
4578
6096
|
|
|
4579
6097
|
// src/mcp/tools.ts
|
|
4580
|
-
import { z as
|
|
4581
|
-
import { join as
|
|
6098
|
+
import { z as z9 } from "zod";
|
|
6099
|
+
import { join as join9 } from "path";
|
|
4582
6100
|
function formatMcpError(code, message) {
|
|
4583
6101
|
return `[${code}] ${message}`;
|
|
4584
6102
|
}
|
|
4585
6103
|
async function runMcpReadTool(pinnedRoot, handler) {
|
|
4586
6104
|
try {
|
|
4587
6105
|
const { state, warnings } = await loadProject(pinnedRoot);
|
|
4588
|
-
const handoversDir =
|
|
6106
|
+
const handoversDir = join9(pinnedRoot, ".story", "handovers");
|
|
4589
6107
|
const ctx = { state, warnings, root: pinnedRoot, handoversDir, format: "md" };
|
|
4590
6108
|
const result = await handler(ctx);
|
|
4591
6109
|
if (result.errorCode && INFRASTRUCTURE_ERROR_CODES.includes(result.errorCode)) {
|
|
@@ -4649,7 +6167,7 @@ function registerAllTools(server, pinnedRoot) {
|
|
|
4649
6167
|
server.registerTool("claudestory_ticket_next", {
|
|
4650
6168
|
description: "Highest-priority unblocked ticket(s) with unblock impact and umbrella progress",
|
|
4651
6169
|
inputSchema: {
|
|
4652
|
-
count:
|
|
6170
|
+
count: z9.number().int().min(1).max(10).optional().describe("Number of candidates to return (default: 1)")
|
|
4653
6171
|
}
|
|
4654
6172
|
}, (args) => runMcpReadTool(
|
|
4655
6173
|
pinnedRoot,
|
|
@@ -4664,7 +6182,7 @@ function registerAllTools(server, pinnedRoot) {
|
|
|
4664
6182
|
server.registerTool("claudestory_handover_latest", {
|
|
4665
6183
|
description: "Content of the most recent handover document(s)",
|
|
4666
6184
|
inputSchema: {
|
|
4667
|
-
count:
|
|
6185
|
+
count: z9.number().int().min(1).max(10).optional().describe("Number of recent handovers to return (default: 1)")
|
|
4668
6186
|
}
|
|
4669
6187
|
}, (args) => runMcpReadTool(
|
|
4670
6188
|
pinnedRoot,
|
|
@@ -4679,7 +6197,7 @@ function registerAllTools(server, pinnedRoot) {
|
|
|
4679
6197
|
server.registerTool("claudestory_phase_tickets", {
|
|
4680
6198
|
description: "Leaf tickets for a specific phase, sorted by order",
|
|
4681
6199
|
inputSchema: {
|
|
4682
|
-
phaseId:
|
|
6200
|
+
phaseId: z9.string().describe("Phase ID (e.g. p5b, dogfood)")
|
|
4683
6201
|
}
|
|
4684
6202
|
}, (args) => runMcpReadTool(pinnedRoot, (ctx) => {
|
|
4685
6203
|
const phaseExists = ctx.state.roadmap.phases.some((p) => p.id === args.phaseId);
|
|
@@ -4695,9 +6213,9 @@ function registerAllTools(server, pinnedRoot) {
|
|
|
4695
6213
|
server.registerTool("claudestory_ticket_list", {
|
|
4696
6214
|
description: "List leaf tickets with optional filters",
|
|
4697
6215
|
inputSchema: {
|
|
4698
|
-
status:
|
|
4699
|
-
phase:
|
|
4700
|
-
type:
|
|
6216
|
+
status: z9.enum(TICKET_STATUSES).optional().describe("Filter by status: open, inprogress, complete"),
|
|
6217
|
+
phase: z9.string().optional().describe("Filter by phase ID"),
|
|
6218
|
+
type: z9.enum(TICKET_TYPES).optional().describe("Filter by type: task, feature, chore")
|
|
4701
6219
|
}
|
|
4702
6220
|
}, (args) => runMcpReadTool(pinnedRoot, (ctx) => {
|
|
4703
6221
|
if (args.phase) {
|
|
@@ -4718,15 +6236,15 @@ function registerAllTools(server, pinnedRoot) {
|
|
|
4718
6236
|
server.registerTool("claudestory_ticket_get", {
|
|
4719
6237
|
description: "Get a ticket by ID (includes umbrella tickets)",
|
|
4720
6238
|
inputSchema: {
|
|
4721
|
-
id:
|
|
6239
|
+
id: z9.string().regex(TICKET_ID_REGEX).describe("Ticket ID (e.g. T-001, T-079b)")
|
|
4722
6240
|
}
|
|
4723
6241
|
}, (args) => runMcpReadTool(pinnedRoot, (ctx) => handleTicketGet(args.id, ctx)));
|
|
4724
6242
|
server.registerTool("claudestory_issue_list", {
|
|
4725
6243
|
description: "List issues with optional filters",
|
|
4726
6244
|
inputSchema: {
|
|
4727
|
-
status:
|
|
4728
|
-
severity:
|
|
4729
|
-
component:
|
|
6245
|
+
status: z9.enum(ISSUE_STATUSES).optional().describe("Filter by status: open, inprogress, resolved"),
|
|
6246
|
+
severity: z9.enum(ISSUE_SEVERITIES).optional().describe("Filter by severity: critical, high, medium, low"),
|
|
6247
|
+
component: z9.string().optional().describe("Filter by component name")
|
|
4730
6248
|
}
|
|
4731
6249
|
}, (args) => runMcpReadTool(
|
|
4732
6250
|
pinnedRoot,
|
|
@@ -4735,13 +6253,13 @@ function registerAllTools(server, pinnedRoot) {
|
|
|
4735
6253
|
server.registerTool("claudestory_issue_get", {
|
|
4736
6254
|
description: "Get an issue by ID",
|
|
4737
6255
|
inputSchema: {
|
|
4738
|
-
id:
|
|
6256
|
+
id: z9.string().regex(ISSUE_ID_REGEX).describe("Issue ID (e.g. ISS-001)")
|
|
4739
6257
|
}
|
|
4740
6258
|
}, (args) => runMcpReadTool(pinnedRoot, (ctx) => handleIssueGet(args.id, ctx)));
|
|
4741
6259
|
server.registerTool("claudestory_handover_get", {
|
|
4742
6260
|
description: "Content of a specific handover document by filename",
|
|
4743
6261
|
inputSchema: {
|
|
4744
|
-
filename:
|
|
6262
|
+
filename: z9.string().describe("Handover filename (e.g. 2026-03-20-session.md)")
|
|
4745
6263
|
}
|
|
4746
6264
|
}, (args) => runMcpReadTool(pinnedRoot, (ctx) => handleHandoverGet(args.filename, ctx)));
|
|
4747
6265
|
server.registerTool("claudestory_recap", {
|
|
@@ -4750,7 +6268,7 @@ function registerAllTools(server, pinnedRoot) {
|
|
|
4750
6268
|
server.registerTool("claudestory_recommend", {
|
|
4751
6269
|
description: "Context-aware ranked work suggestions mixing tickets and issues",
|
|
4752
6270
|
inputSchema: {
|
|
4753
|
-
count:
|
|
6271
|
+
count: z9.number().int().min(1).max(10).optional().describe("Number of recommendations (default: 5)")
|
|
4754
6272
|
}
|
|
4755
6273
|
}, (args) => runMcpReadTool(
|
|
4756
6274
|
pinnedRoot,
|
|
@@ -4762,8 +6280,8 @@ function registerAllTools(server, pinnedRoot) {
|
|
|
4762
6280
|
server.registerTool("claudestory_export", {
|
|
4763
6281
|
description: "Self-contained project document for sharing",
|
|
4764
6282
|
inputSchema: {
|
|
4765
|
-
phase:
|
|
4766
|
-
all:
|
|
6283
|
+
phase: z9.string().optional().describe("Export a single phase by ID"),
|
|
6284
|
+
all: z9.boolean().optional().describe("Export entire project")
|
|
4767
6285
|
}
|
|
4768
6286
|
}, (args) => {
|
|
4769
6287
|
if (!args.phase && !args.all) {
|
|
@@ -4785,8 +6303,8 @@ function registerAllTools(server, pinnedRoot) {
|
|
|
4785
6303
|
server.registerTool("claudestory_handover_create", {
|
|
4786
6304
|
description: "Create a handover document from markdown content",
|
|
4787
6305
|
inputSchema: {
|
|
4788
|
-
content:
|
|
4789
|
-
slug:
|
|
6306
|
+
content: z9.string().describe("Markdown content of the handover"),
|
|
6307
|
+
slug: z9.string().optional().describe("Slug for filename (e.g. phase5b-wrapup). Default: session")
|
|
4790
6308
|
}
|
|
4791
6309
|
}, (args) => {
|
|
4792
6310
|
if (!args.content?.trim()) {
|
|
@@ -4803,12 +6321,12 @@ function registerAllTools(server, pinnedRoot) {
|
|
|
4803
6321
|
server.registerTool("claudestory_ticket_create", {
|
|
4804
6322
|
description: "Create a new ticket",
|
|
4805
6323
|
inputSchema: {
|
|
4806
|
-
title:
|
|
4807
|
-
type:
|
|
4808
|
-
phase:
|
|
4809
|
-
description:
|
|
4810
|
-
blockedBy:
|
|
4811
|
-
parentTicket:
|
|
6324
|
+
title: z9.string().describe("Ticket title"),
|
|
6325
|
+
type: z9.enum(TICKET_TYPES).describe("Ticket type: task, feature, chore"),
|
|
6326
|
+
phase: z9.string().optional().describe("Phase ID"),
|
|
6327
|
+
description: z9.string().optional().describe("Ticket description"),
|
|
6328
|
+
blockedBy: z9.array(z9.string().regex(TICKET_ID_REGEX)).optional().describe("IDs of blocking tickets"),
|
|
6329
|
+
parentTicket: z9.string().regex(TICKET_ID_REGEX).optional().describe("Parent ticket ID (makes this a sub-ticket)")
|
|
4812
6330
|
}
|
|
4813
6331
|
}, (args) => runMcpWriteTool(
|
|
4814
6332
|
pinnedRoot,
|
|
@@ -4828,15 +6346,15 @@ function registerAllTools(server, pinnedRoot) {
|
|
|
4828
6346
|
server.registerTool("claudestory_ticket_update", {
|
|
4829
6347
|
description: "Update an existing ticket",
|
|
4830
6348
|
inputSchema: {
|
|
4831
|
-
id:
|
|
4832
|
-
status:
|
|
4833
|
-
title:
|
|
4834
|
-
type:
|
|
4835
|
-
order:
|
|
4836
|
-
description:
|
|
4837
|
-
phase:
|
|
4838
|
-
parentTicket:
|
|
4839
|
-
blockedBy:
|
|
6349
|
+
id: z9.string().regex(TICKET_ID_REGEX).describe("Ticket ID (e.g. T-001)"),
|
|
6350
|
+
status: z9.enum(TICKET_STATUSES).optional().describe("New status: open, inprogress, complete"),
|
|
6351
|
+
title: z9.string().optional().describe("New title"),
|
|
6352
|
+
type: z9.enum(TICKET_TYPES).optional().describe("New type: task, feature, chore"),
|
|
6353
|
+
order: z9.number().int().optional().describe("New sort order"),
|
|
6354
|
+
description: z9.string().optional().describe("New description"),
|
|
6355
|
+
phase: z9.string().nullable().optional().describe("New phase ID (null to clear)"),
|
|
6356
|
+
parentTicket: z9.string().regex(TICKET_ID_REGEX).nullable().optional().describe("Parent ticket ID (null to clear)"),
|
|
6357
|
+
blockedBy: z9.array(z9.string().regex(TICKET_ID_REGEX)).optional().describe("IDs of blocking tickets")
|
|
4840
6358
|
}
|
|
4841
6359
|
}, (args) => runMcpWriteTool(
|
|
4842
6360
|
pinnedRoot,
|
|
@@ -4859,13 +6377,13 @@ function registerAllTools(server, pinnedRoot) {
|
|
|
4859
6377
|
server.registerTool("claudestory_issue_create", {
|
|
4860
6378
|
description: "Create a new issue",
|
|
4861
6379
|
inputSchema: {
|
|
4862
|
-
title:
|
|
4863
|
-
severity:
|
|
4864
|
-
impact:
|
|
4865
|
-
components:
|
|
4866
|
-
relatedTickets:
|
|
4867
|
-
location:
|
|
4868
|
-
phase:
|
|
6380
|
+
title: z9.string().describe("Issue title"),
|
|
6381
|
+
severity: z9.enum(ISSUE_SEVERITIES).describe("Issue severity: critical, high, medium, low"),
|
|
6382
|
+
impact: z9.string().describe("Impact description"),
|
|
6383
|
+
components: z9.array(z9.string()).optional().describe("Affected components"),
|
|
6384
|
+
relatedTickets: z9.array(z9.string().regex(TICKET_ID_REGEX)).optional().describe("Related ticket IDs"),
|
|
6385
|
+
location: z9.array(z9.string()).optional().describe("File locations"),
|
|
6386
|
+
phase: z9.string().optional().describe("Phase ID")
|
|
4869
6387
|
}
|
|
4870
6388
|
}, (args) => runMcpWriteTool(
|
|
4871
6389
|
pinnedRoot,
|
|
@@ -4886,17 +6404,17 @@ function registerAllTools(server, pinnedRoot) {
|
|
|
4886
6404
|
server.registerTool("claudestory_issue_update", {
|
|
4887
6405
|
description: "Update an existing issue",
|
|
4888
6406
|
inputSchema: {
|
|
4889
|
-
id:
|
|
4890
|
-
status:
|
|
4891
|
-
title:
|
|
4892
|
-
severity:
|
|
4893
|
-
impact:
|
|
4894
|
-
resolution:
|
|
4895
|
-
components:
|
|
4896
|
-
relatedTickets:
|
|
4897
|
-
location:
|
|
4898
|
-
order:
|
|
4899
|
-
phase:
|
|
6407
|
+
id: z9.string().regex(ISSUE_ID_REGEX).describe("Issue ID (e.g. ISS-001)"),
|
|
6408
|
+
status: z9.enum(ISSUE_STATUSES).optional().describe("New status: open, inprogress, resolved"),
|
|
6409
|
+
title: z9.string().optional().describe("New title"),
|
|
6410
|
+
severity: z9.enum(ISSUE_SEVERITIES).optional().describe("New severity"),
|
|
6411
|
+
impact: z9.string().optional().describe("New impact description"),
|
|
6412
|
+
resolution: z9.string().nullable().optional().describe("Resolution description (null to clear)"),
|
|
6413
|
+
components: z9.array(z9.string()).optional().describe("Affected components"),
|
|
6414
|
+
relatedTickets: z9.array(z9.string().regex(TICKET_ID_REGEX)).optional().describe("Related ticket IDs"),
|
|
6415
|
+
location: z9.array(z9.string()).optional().describe("File locations"),
|
|
6416
|
+
order: z9.number().int().optional().describe("New sort order"),
|
|
6417
|
+
phase: z9.string().nullable().optional().describe("New phase ID (null to clear)")
|
|
4900
6418
|
}
|
|
4901
6419
|
}, (args) => runMcpWriteTool(
|
|
4902
6420
|
pinnedRoot,
|
|
@@ -4921,8 +6439,8 @@ function registerAllTools(server, pinnedRoot) {
|
|
|
4921
6439
|
server.registerTool("claudestory_note_list", {
|
|
4922
6440
|
description: "List notes with optional status/tag filters",
|
|
4923
6441
|
inputSchema: {
|
|
4924
|
-
status:
|
|
4925
|
-
tag:
|
|
6442
|
+
status: z9.enum(NOTE_STATUSES).optional().describe("Filter by status: active, archived"),
|
|
6443
|
+
tag: z9.string().optional().describe("Filter by tag")
|
|
4926
6444
|
}
|
|
4927
6445
|
}, (args) => runMcpReadTool(
|
|
4928
6446
|
pinnedRoot,
|
|
@@ -4931,15 +6449,15 @@ function registerAllTools(server, pinnedRoot) {
|
|
|
4931
6449
|
server.registerTool("claudestory_note_get", {
|
|
4932
6450
|
description: "Get a note by ID",
|
|
4933
6451
|
inputSchema: {
|
|
4934
|
-
id:
|
|
6452
|
+
id: z9.string().regex(NOTE_ID_REGEX).describe("Note ID (e.g. N-001)")
|
|
4935
6453
|
}
|
|
4936
6454
|
}, (args) => runMcpReadTool(pinnedRoot, (ctx) => handleNoteGet(args.id, ctx)));
|
|
4937
6455
|
server.registerTool("claudestory_note_create", {
|
|
4938
6456
|
description: "Create a new note",
|
|
4939
6457
|
inputSchema: {
|
|
4940
|
-
content:
|
|
4941
|
-
title:
|
|
4942
|
-
tags:
|
|
6458
|
+
content: z9.string().describe("Note content"),
|
|
6459
|
+
title: z9.string().optional().describe("Note title"),
|
|
6460
|
+
tags: z9.array(z9.string()).optional().describe("Tags for the note")
|
|
4943
6461
|
}
|
|
4944
6462
|
}, (args) => runMcpWriteTool(
|
|
4945
6463
|
pinnedRoot,
|
|
@@ -4956,11 +6474,11 @@ function registerAllTools(server, pinnedRoot) {
|
|
|
4956
6474
|
server.registerTool("claudestory_note_update", {
|
|
4957
6475
|
description: "Update an existing note",
|
|
4958
6476
|
inputSchema: {
|
|
4959
|
-
id:
|
|
4960
|
-
content:
|
|
4961
|
-
title:
|
|
4962
|
-
tags:
|
|
4963
|
-
status:
|
|
6477
|
+
id: z9.string().regex(NOTE_ID_REGEX).describe("Note ID (e.g. N-001)"),
|
|
6478
|
+
content: z9.string().optional().describe("New content"),
|
|
6479
|
+
title: z9.string().nullable().optional().describe("New title (null to clear)"),
|
|
6480
|
+
tags: z9.array(z9.string()).optional().describe("New tags (replaces existing)"),
|
|
6481
|
+
status: z9.enum(NOTE_STATUSES).optional().describe("New status: active, archived")
|
|
4964
6482
|
}
|
|
4965
6483
|
}, (args) => runMcpWriteTool(
|
|
4966
6484
|
pinnedRoot,
|
|
@@ -4980,13 +6498,13 @@ function registerAllTools(server, pinnedRoot) {
|
|
|
4980
6498
|
server.registerTool("claudestory_phase_create", {
|
|
4981
6499
|
description: "Create a new phase in the roadmap. Exactly one of after or atStart is required for positioning.",
|
|
4982
6500
|
inputSchema: {
|
|
4983
|
-
id:
|
|
4984
|
-
name:
|
|
4985
|
-
label:
|
|
4986
|
-
description:
|
|
4987
|
-
summary:
|
|
4988
|
-
after:
|
|
4989
|
-
atStart:
|
|
6501
|
+
id: z9.string().describe("Phase ID \u2014 lowercase alphanumeric with hyphens (e.g. 'my-phase')"),
|
|
6502
|
+
name: z9.string().describe("Phase display name"),
|
|
6503
|
+
label: z9.string().describe("Phase label (e.g. 'PHASE 1')"),
|
|
6504
|
+
description: z9.string().describe("Phase description"),
|
|
6505
|
+
summary: z9.string().optional().describe("One-line summary for compact display"),
|
|
6506
|
+
after: z9.string().optional().describe("Insert after this phase ID"),
|
|
6507
|
+
atStart: z9.boolean().optional().describe("Insert at beginning of roadmap")
|
|
4990
6508
|
}
|
|
4991
6509
|
}, (args) => runMcpWriteTool(
|
|
4992
6510
|
pinnedRoot,
|
|
@@ -5010,6 +6528,29 @@ function registerAllTools(server, pinnedRoot) {
|
|
|
5010
6528
|
pinnedRoot,
|
|
5011
6529
|
(root, format) => handleSelftest(root, format)
|
|
5012
6530
|
));
|
|
6531
|
+
server.registerTool("claudestory_autonomous_guide", {
|
|
6532
|
+
description: "Autonomous session orchestrator. Call at every decision point during autonomous mode.",
|
|
6533
|
+
inputSchema: {
|
|
6534
|
+
sessionId: z9.string().uuid().nullable().describe("Session ID (null for start action)"),
|
|
6535
|
+
action: z9.enum(["start", "report", "resume", "pre_compact", "cancel"]).describe("Action to perform"),
|
|
6536
|
+
report: z9.object({
|
|
6537
|
+
completedAction: z9.string().describe("What was completed"),
|
|
6538
|
+
ticketId: z9.string().optional().describe("Ticket ID (for ticket_picked)"),
|
|
6539
|
+
commitHash: z9.string().optional().describe("Git commit hash (for commit_done)"),
|
|
6540
|
+
handoverContent: z9.string().optional().describe("Handover markdown content"),
|
|
6541
|
+
verdict: z9.string().optional().describe("Review verdict: approve|revise|request_changes|reject"),
|
|
6542
|
+
findings: z9.array(z9.object({
|
|
6543
|
+
id: z9.string(),
|
|
6544
|
+
severity: z9.string(),
|
|
6545
|
+
category: z9.string(),
|
|
6546
|
+
description: z9.string(),
|
|
6547
|
+
disposition: z9.string()
|
|
6548
|
+
})).optional().describe("Review findings"),
|
|
6549
|
+
reviewerSessionId: z9.string().optional().describe("Codex session ID"),
|
|
6550
|
+
notes: z9.string().optional().describe("Free-text notes")
|
|
6551
|
+
}).optional().describe("Report data (required for report action)")
|
|
6552
|
+
}
|
|
6553
|
+
}, (args) => handleAutonomousGuide(pinnedRoot, args));
|
|
5013
6554
|
}
|
|
5014
6555
|
var INFRASTRUCTURE_ERROR_CODES;
|
|
5015
6556
|
var init_tools = __esm({
|
|
@@ -5033,6 +6574,7 @@ var init_tools = __esm({
|
|
|
5033
6574
|
init_export();
|
|
5034
6575
|
init_selftest();
|
|
5035
6576
|
init_handover();
|
|
6577
|
+
init_guide();
|
|
5036
6578
|
init_phase();
|
|
5037
6579
|
INFRASTRUCTURE_ERROR_CODES = [
|
|
5038
6580
|
"io_error",
|
|
@@ -5043,11 +6585,11 @@ var init_tools = __esm({
|
|
|
5043
6585
|
});
|
|
5044
6586
|
|
|
5045
6587
|
// src/core/init.ts
|
|
5046
|
-
import { mkdir as mkdir4, stat as stat2 } from "fs/promises";
|
|
5047
|
-
import { join as
|
|
6588
|
+
import { mkdir as mkdir4, stat as stat2, readFile as readFile4, writeFile as writeFile2 } from "fs/promises";
|
|
6589
|
+
import { join as join10, resolve as resolve7 } from "path";
|
|
5048
6590
|
async function initProject(root, options) {
|
|
5049
6591
|
const absRoot = resolve7(root);
|
|
5050
|
-
const wrapDir =
|
|
6592
|
+
const wrapDir = join10(absRoot, ".story");
|
|
5051
6593
|
let exists = false;
|
|
5052
6594
|
try {
|
|
5053
6595
|
const s = await stat2(wrapDir);
|
|
@@ -5067,10 +6609,10 @@ async function initProject(root, options) {
|
|
|
5067
6609
|
".story/ already exists. Use --force to overwrite config and roadmap."
|
|
5068
6610
|
);
|
|
5069
6611
|
}
|
|
5070
|
-
await mkdir4(
|
|
5071
|
-
await mkdir4(
|
|
5072
|
-
await mkdir4(
|
|
5073
|
-
await mkdir4(
|
|
6612
|
+
await mkdir4(join10(wrapDir, "tickets"), { recursive: true });
|
|
6613
|
+
await mkdir4(join10(wrapDir, "issues"), { recursive: true });
|
|
6614
|
+
await mkdir4(join10(wrapDir, "handovers"), { recursive: true });
|
|
6615
|
+
await mkdir4(join10(wrapDir, "notes"), { recursive: true });
|
|
5074
6616
|
const created = [
|
|
5075
6617
|
".story/config.json",
|
|
5076
6618
|
".story/roadmap.json",
|
|
@@ -5109,6 +6651,8 @@ async function initProject(root, options) {
|
|
|
5109
6651
|
};
|
|
5110
6652
|
await writeConfig(config, absRoot);
|
|
5111
6653
|
await writeRoadmap(roadmap, absRoot);
|
|
6654
|
+
const gitignorePath = join10(wrapDir, ".gitignore");
|
|
6655
|
+
await ensureGitignoreEntries(gitignorePath, STORY_GITIGNORE_ENTRIES);
|
|
5112
6656
|
const warnings = [];
|
|
5113
6657
|
if (options.force && exists) {
|
|
5114
6658
|
try {
|
|
@@ -5127,20 +6671,36 @@ async function initProject(root, options) {
|
|
|
5127
6671
|
warnings
|
|
5128
6672
|
};
|
|
5129
6673
|
}
|
|
6674
|
+
async function ensureGitignoreEntries(gitignorePath, entries) {
|
|
6675
|
+
let existing = "";
|
|
6676
|
+
try {
|
|
6677
|
+
existing = await readFile4(gitignorePath, "utf-8");
|
|
6678
|
+
} catch {
|
|
6679
|
+
}
|
|
6680
|
+
const lines = existing.split("\n").map((l) => l.trim());
|
|
6681
|
+
const missing = entries.filter((e) => !lines.includes(e));
|
|
6682
|
+
if (missing.length === 0) return;
|
|
6683
|
+
let content = existing;
|
|
6684
|
+
if (content.length > 0 && !content.endsWith("\n")) content += "\n";
|
|
6685
|
+
content += missing.join("\n") + "\n";
|
|
6686
|
+
await writeFile2(gitignorePath, content, "utf-8");
|
|
6687
|
+
}
|
|
6688
|
+
var STORY_GITIGNORE_ENTRIES;
|
|
5130
6689
|
var init_init = __esm({
|
|
5131
6690
|
"src/core/init.ts"() {
|
|
5132
6691
|
"use strict";
|
|
5133
6692
|
init_esm_shims();
|
|
5134
6693
|
init_project_loader();
|
|
5135
6694
|
init_errors();
|
|
6695
|
+
STORY_GITIGNORE_ENTRIES = ["snapshots/", "status.json", "sessions/"];
|
|
5136
6696
|
}
|
|
5137
6697
|
});
|
|
5138
6698
|
|
|
5139
6699
|
// src/mcp/index.ts
|
|
5140
6700
|
var mcp_exports = {};
|
|
5141
|
-
import { realpathSync, existsSync as
|
|
5142
|
-
import { resolve as resolve8, join as
|
|
5143
|
-
import { z as
|
|
6701
|
+
import { realpathSync as realpathSync2, existsSync as existsSync8 } from "fs";
|
|
6702
|
+
import { resolve as resolve8, join as join11, isAbsolute } from "path";
|
|
6703
|
+
import { z as z10 } from "zod";
|
|
5144
6704
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
5145
6705
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
5146
6706
|
function tryDiscoverRoot() {
|
|
@@ -5153,8 +6713,8 @@ function tryDiscoverRoot() {
|
|
|
5153
6713
|
}
|
|
5154
6714
|
const resolved = resolve8(envRoot);
|
|
5155
6715
|
try {
|
|
5156
|
-
const canonical =
|
|
5157
|
-
if (
|
|
6716
|
+
const canonical = realpathSync2(resolved);
|
|
6717
|
+
if (existsSync8(join11(canonical, CONFIG_PATH2))) {
|
|
5158
6718
|
return canonical;
|
|
5159
6719
|
}
|
|
5160
6720
|
process.stderr.write(`Warning: No .story/config.json at ${canonical}
|
|
@@ -5167,7 +6727,7 @@ function tryDiscoverRoot() {
|
|
|
5167
6727
|
}
|
|
5168
6728
|
try {
|
|
5169
6729
|
const root = discoverProjectRoot();
|
|
5170
|
-
return root ?
|
|
6730
|
+
return root ? realpathSync2(root) : null;
|
|
5171
6731
|
} catch {
|
|
5172
6732
|
return null;
|
|
5173
6733
|
}
|
|
@@ -5182,14 +6742,14 @@ function registerDegradedTools(server) {
|
|
|
5182
6742
|
const degradedInit = server.registerTool("claudestory_init", {
|
|
5183
6743
|
description: "Initialize a new .story/ project in the current directory",
|
|
5184
6744
|
inputSchema: {
|
|
5185
|
-
name:
|
|
5186
|
-
type:
|
|
5187
|
-
language:
|
|
6745
|
+
name: z10.string().describe("Project name"),
|
|
6746
|
+
type: z10.string().optional().describe("Project type (e.g. npm, macapp, cargo, generic)"),
|
|
6747
|
+
language: z10.string().optional().describe("Primary language (e.g. typescript, swift, rust)")
|
|
5188
6748
|
}
|
|
5189
6749
|
}, async (args) => {
|
|
5190
6750
|
let result;
|
|
5191
6751
|
try {
|
|
5192
|
-
const projectRoot =
|
|
6752
|
+
const projectRoot = realpathSync2(process.cwd());
|
|
5193
6753
|
result = await initProject(projectRoot, {
|
|
5194
6754
|
name: args.name,
|
|
5195
6755
|
type: args.type,
|
|
@@ -5257,7 +6817,7 @@ var init_mcp = __esm({
|
|
|
5257
6817
|
init_init();
|
|
5258
6818
|
ENV_VAR2 = "CLAUDESTORY_PROJECT_ROOT";
|
|
5259
6819
|
CONFIG_PATH2 = ".story/config.json";
|
|
5260
|
-
version = "0.1.
|
|
6820
|
+
version = "0.1.13";
|
|
5261
6821
|
main().catch((err) => {
|
|
5262
6822
|
process.stderr.write(`Fatal: ${err instanceof Error ? err.message : String(err)}
|
|
5263
6823
|
`);
|
|
@@ -5293,7 +6853,7 @@ __export(run_exports, {
|
|
|
5293
6853
|
runReadCommand: () => runReadCommand,
|
|
5294
6854
|
writeOutput: () => writeOutput
|
|
5295
6855
|
});
|
|
5296
|
-
import { join as
|
|
6856
|
+
import { join as join12 } from "path";
|
|
5297
6857
|
function writeOutput(text) {
|
|
5298
6858
|
try {
|
|
5299
6859
|
process.stdout.write(text + "\n");
|
|
@@ -5321,7 +6881,7 @@ async function runReadCommand(format, handler) {
|
|
|
5321
6881
|
return;
|
|
5322
6882
|
}
|
|
5323
6883
|
const { state, warnings } = await loadProject(root);
|
|
5324
|
-
const handoversDir =
|
|
6884
|
+
const handoversDir = join12(root, ".story", "handovers");
|
|
5325
6885
|
const result = await handler({ state, warnings, root, handoversDir, format });
|
|
5326
6886
|
writeOutput(result.output);
|
|
5327
6887
|
let exitCode = result.exitCode ?? ExitCode.OK;
|
|
@@ -5356,7 +6916,7 @@ async function runDeleteCommand(format, force, handler) {
|
|
|
5356
6916
|
return;
|
|
5357
6917
|
}
|
|
5358
6918
|
const { state, warnings } = await loadProject(root);
|
|
5359
|
-
const handoversDir =
|
|
6919
|
+
const handoversDir = join12(root, ".story", "handovers");
|
|
5360
6920
|
if (!force && hasIntegrityWarnings(warnings)) {
|
|
5361
6921
|
writeOutput(
|
|
5362
6922
|
formatError(
|
|
@@ -5761,11 +7321,12 @@ var setup_skill_exports = {};
|
|
|
5761
7321
|
__export(setup_skill_exports, {
|
|
5762
7322
|
handleSetupSkill: () => handleSetupSkill,
|
|
5763
7323
|
registerPreCompactHook: () => registerPreCompactHook,
|
|
7324
|
+
registerStopHook: () => registerStopHook,
|
|
5764
7325
|
resolveSkillSourceDir: () => resolveSkillSourceDir
|
|
5765
7326
|
});
|
|
5766
|
-
import { mkdir as mkdir5, writeFile as
|
|
5767
|
-
import { existsSync as
|
|
5768
|
-
import { join as
|
|
7327
|
+
import { mkdir as mkdir5, writeFile as writeFile3, readFile as readFile5, rm, rename as rename2, unlink as unlink3 } from "fs/promises";
|
|
7328
|
+
import { existsSync as existsSync9 } from "fs";
|
|
7329
|
+
import { join as join13, dirname as dirname3 } from "path";
|
|
5769
7330
|
import { homedir } from "os";
|
|
5770
7331
|
import { execFileSync } from "child_process";
|
|
5771
7332
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
@@ -5774,27 +7335,27 @@ function log(msg) {
|
|
|
5774
7335
|
}
|
|
5775
7336
|
function resolveSkillSourceDir() {
|
|
5776
7337
|
const thisDir = dirname3(fileURLToPath2(import.meta.url));
|
|
5777
|
-
const bundledPath =
|
|
5778
|
-
if (
|
|
5779
|
-
const sourcePath =
|
|
5780
|
-
if (
|
|
7338
|
+
const bundledPath = join13(thisDir, "..", "src", "skill");
|
|
7339
|
+
if (existsSync9(join13(bundledPath, "SKILL.md"))) return bundledPath;
|
|
7340
|
+
const sourcePath = join13(thisDir, "..", "..", "skill");
|
|
7341
|
+
if (existsSync9(join13(sourcePath, "SKILL.md"))) return sourcePath;
|
|
5781
7342
|
throw new Error(
|
|
5782
7343
|
`Cannot find bundled skill files. Checked:
|
|
5783
7344
|
${bundledPath}
|
|
5784
7345
|
${sourcePath}`
|
|
5785
7346
|
);
|
|
5786
7347
|
}
|
|
5787
|
-
function
|
|
7348
|
+
function isHookWithCommand(entry, command) {
|
|
5788
7349
|
if (typeof entry !== "object" || entry === null) return false;
|
|
5789
7350
|
const e = entry;
|
|
5790
|
-
return e.type === "command" && typeof e.command === "string" && e.command.trim() ===
|
|
7351
|
+
return e.type === "command" && typeof e.command === "string" && e.command.trim() === command;
|
|
5791
7352
|
}
|
|
5792
|
-
async function
|
|
5793
|
-
const path2 = settingsPath ??
|
|
7353
|
+
async function registerHook(hookType, hookEntry, settingsPath) {
|
|
7354
|
+
const path2 = settingsPath ?? join13(homedir(), ".claude", "settings.json");
|
|
5794
7355
|
let raw = "{}";
|
|
5795
|
-
if (
|
|
7356
|
+
if (existsSync9(path2)) {
|
|
5796
7357
|
try {
|
|
5797
|
-
raw = await
|
|
7358
|
+
raw = await readFile5(path2, "utf-8");
|
|
5798
7359
|
} catch {
|
|
5799
7360
|
process.stderr.write(`Could not read ${path2} \u2014 skipping hook registration.
|
|
5800
7361
|
`);
|
|
@@ -5825,43 +7386,45 @@ async function registerPreCompactHook(settingsPath) {
|
|
|
5825
7386
|
settings.hooks = {};
|
|
5826
7387
|
}
|
|
5827
7388
|
const hooks = settings.hooks;
|
|
5828
|
-
if (
|
|
5829
|
-
if (!Array.isArray(hooks
|
|
5830
|
-
process.stderr.write(`${path2} has unexpected hooks
|
|
7389
|
+
if (hookType in hooks) {
|
|
7390
|
+
if (!Array.isArray(hooks[hookType])) {
|
|
7391
|
+
process.stderr.write(`${path2} has unexpected hooks.${hookType} format \u2014 skipping hook registration.
|
|
5831
7392
|
`);
|
|
5832
7393
|
return "skipped";
|
|
5833
7394
|
}
|
|
5834
7395
|
} else {
|
|
5835
|
-
hooks
|
|
7396
|
+
hooks[hookType] = [];
|
|
5836
7397
|
}
|
|
5837
|
-
const
|
|
5838
|
-
|
|
5839
|
-
|
|
5840
|
-
const
|
|
5841
|
-
|
|
5842
|
-
|
|
5843
|
-
if (
|
|
7398
|
+
const hookArray = hooks[hookType];
|
|
7399
|
+
const hookCommand = hookEntry.command;
|
|
7400
|
+
if (hookCommand) {
|
|
7401
|
+
for (const group of hookArray) {
|
|
7402
|
+
if (typeof group !== "object" || group === null) continue;
|
|
7403
|
+
const g = group;
|
|
7404
|
+
if (!Array.isArray(g.hooks)) continue;
|
|
7405
|
+
for (const entry of g.hooks) {
|
|
7406
|
+
if (isHookWithCommand(entry, hookCommand)) return "exists";
|
|
7407
|
+
}
|
|
5844
7408
|
}
|
|
5845
7409
|
}
|
|
5846
|
-
const ourEntry = { type: "command", command: HOOK_COMMAND };
|
|
5847
7410
|
let appended = false;
|
|
5848
|
-
for (const group of
|
|
7411
|
+
for (const group of hookArray) {
|
|
5849
7412
|
if (typeof group !== "object" || group === null) continue;
|
|
5850
7413
|
const g = group;
|
|
5851
7414
|
if (g.matcher === "" && Array.isArray(g.hooks)) {
|
|
5852
|
-
g.hooks.push(
|
|
7415
|
+
g.hooks.push(hookEntry);
|
|
5853
7416
|
appended = true;
|
|
5854
7417
|
break;
|
|
5855
7418
|
}
|
|
5856
7419
|
}
|
|
5857
7420
|
if (!appended) {
|
|
5858
|
-
|
|
7421
|
+
hookArray.push({ matcher: "", hooks: [hookEntry] });
|
|
5859
7422
|
}
|
|
5860
7423
|
const tmpPath = `${path2}.${process.pid}.tmp`;
|
|
5861
7424
|
try {
|
|
5862
7425
|
const dir = dirname3(path2);
|
|
5863
7426
|
await mkdir5(dir, { recursive: true });
|
|
5864
|
-
await
|
|
7427
|
+
await writeFile3(tmpPath, JSON.stringify(settings, null, 2) + "\n", "utf-8");
|
|
5865
7428
|
await rename2(tmpPath, path2);
|
|
5866
7429
|
} catch (err) {
|
|
5867
7430
|
try {
|
|
@@ -5875,9 +7438,15 @@ async function registerPreCompactHook(settingsPath) {
|
|
|
5875
7438
|
}
|
|
5876
7439
|
return "registered";
|
|
5877
7440
|
}
|
|
7441
|
+
async function registerPreCompactHook(settingsPath) {
|
|
7442
|
+
return registerHook("PreCompact", { type: "command", command: PRECOMPACT_HOOK_COMMAND }, settingsPath);
|
|
7443
|
+
}
|
|
7444
|
+
async function registerStopHook(settingsPath) {
|
|
7445
|
+
return registerHook("Stop", { type: "command", command: STOP_HOOK_COMMAND, async: true }, settingsPath);
|
|
7446
|
+
}
|
|
5878
7447
|
async function handleSetupSkill(options = {}) {
|
|
5879
7448
|
const { skipHooks = false } = options;
|
|
5880
|
-
const skillDir =
|
|
7449
|
+
const skillDir = join13(homedir(), ".claude", "skills", "story");
|
|
5881
7450
|
await mkdir5(skillDir, { recursive: true });
|
|
5882
7451
|
let srcSkillDir;
|
|
5883
7452
|
try {
|
|
@@ -5890,19 +7459,19 @@ async function handleSetupSkill(options = {}) {
|
|
|
5890
7459
|
process.exitCode = 1;
|
|
5891
7460
|
return;
|
|
5892
7461
|
}
|
|
5893
|
-
const oldPrimeDir =
|
|
5894
|
-
if (
|
|
7462
|
+
const oldPrimeDir = join13(homedir(), ".claude", "skills", "prime");
|
|
7463
|
+
if (existsSync9(oldPrimeDir)) {
|
|
5895
7464
|
await rm(oldPrimeDir, { recursive: true, force: true });
|
|
5896
7465
|
log("Removed old /prime skill (migrated to /story)");
|
|
5897
7466
|
}
|
|
5898
|
-
const existed =
|
|
5899
|
-
const skillContent = await
|
|
5900
|
-
await
|
|
7467
|
+
const existed = existsSync9(join13(skillDir, "SKILL.md"));
|
|
7468
|
+
const skillContent = await readFile5(join13(srcSkillDir, "SKILL.md"), "utf-8");
|
|
7469
|
+
await writeFile3(join13(skillDir, "SKILL.md"), skillContent, "utf-8");
|
|
5901
7470
|
let referenceWritten = false;
|
|
5902
|
-
const refSrcPath =
|
|
5903
|
-
if (
|
|
5904
|
-
const refContent = await
|
|
5905
|
-
await
|
|
7471
|
+
const refSrcPath = join13(srcSkillDir, "reference.md");
|
|
7472
|
+
if (existsSync9(refSrcPath)) {
|
|
7473
|
+
const refContent = await readFile5(refSrcPath, "utf-8");
|
|
7474
|
+
await writeFile3(join13(skillDir, "reference.md"), refContent, "utf-8");
|
|
5906
7475
|
referenceWritten = true;
|
|
5907
7476
|
}
|
|
5908
7477
|
log(`${existed ? "Updated" : "Installed"} /story skill at ${skillDir}/`);
|
|
@@ -5966,6 +7535,21 @@ async function handleSetupSkill(options = {}) {
|
|
|
5966
7535
|
} else if (skipHooks) {
|
|
5967
7536
|
log(" Hook registration skipped (--skip-hooks)");
|
|
5968
7537
|
}
|
|
7538
|
+
if (cliInPath && !skipHooks) {
|
|
7539
|
+
const stopResult = await registerStopHook();
|
|
7540
|
+
switch (stopResult) {
|
|
7541
|
+
case "registered":
|
|
7542
|
+
log(" Stop hook registered \u2014 status.json updated after every Claude response");
|
|
7543
|
+
break;
|
|
7544
|
+
case "exists":
|
|
7545
|
+
log(" Stop hook already configured");
|
|
7546
|
+
break;
|
|
7547
|
+
case "skipped":
|
|
7548
|
+
break;
|
|
7549
|
+
}
|
|
7550
|
+
} else if (!cliInPath) {
|
|
7551
|
+
} else if (skipHooks) {
|
|
7552
|
+
}
|
|
5969
7553
|
log("");
|
|
5970
7554
|
if (mcpRegistered) {
|
|
5971
7555
|
log("Done! Restart Claude Code, then type /story in any project.");
|
|
@@ -5973,12 +7557,142 @@ async function handleSetupSkill(options = {}) {
|
|
|
5973
7557
|
log("Skill installed. After registering MCP, restart Claude Code and type /story.");
|
|
5974
7558
|
}
|
|
5975
7559
|
}
|
|
5976
|
-
var
|
|
7560
|
+
var PRECOMPACT_HOOK_COMMAND, STOP_HOOK_COMMAND;
|
|
5977
7561
|
var init_setup_skill = __esm({
|
|
5978
7562
|
"src/cli/commands/setup-skill.ts"() {
|
|
5979
7563
|
"use strict";
|
|
5980
7564
|
init_esm_shims();
|
|
5981
|
-
|
|
7565
|
+
PRECOMPACT_HOOK_COMMAND = "claudestory snapshot --quiet";
|
|
7566
|
+
STOP_HOOK_COMMAND = "claudestory hook-status";
|
|
7567
|
+
}
|
|
7568
|
+
});
|
|
7569
|
+
|
|
7570
|
+
// src/cli/commands/hook-status.ts
|
|
7571
|
+
var hook_status_exports = {};
|
|
7572
|
+
__export(hook_status_exports, {
|
|
7573
|
+
handleHookStatus: () => handleHookStatus
|
|
7574
|
+
});
|
|
7575
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, renameSync as renameSync2, unlinkSync as unlinkSync2 } from "fs";
|
|
7576
|
+
import { join as join14 } from "path";
|
|
7577
|
+
async function readStdinSilent() {
|
|
7578
|
+
try {
|
|
7579
|
+
const chunks = [];
|
|
7580
|
+
for await (const chunk of process.stdin) {
|
|
7581
|
+
chunks.push(chunk);
|
|
7582
|
+
}
|
|
7583
|
+
return Buffer.concat(
|
|
7584
|
+
chunks.map((c) => Buffer.isBuffer(c) ? c : Buffer.from(c))
|
|
7585
|
+
).toString("utf-8");
|
|
7586
|
+
} catch {
|
|
7587
|
+
return null;
|
|
7588
|
+
}
|
|
7589
|
+
}
|
|
7590
|
+
function atomicWriteSync(targetPath, content) {
|
|
7591
|
+
const tmp = `${targetPath}.${process.pid}.tmp`;
|
|
7592
|
+
try {
|
|
7593
|
+
writeFileSync3(tmp, content, "utf-8");
|
|
7594
|
+
renameSync2(tmp, targetPath);
|
|
7595
|
+
return true;
|
|
7596
|
+
} catch {
|
|
7597
|
+
try {
|
|
7598
|
+
unlinkSync2(tmp);
|
|
7599
|
+
} catch {
|
|
7600
|
+
}
|
|
7601
|
+
return false;
|
|
7602
|
+
}
|
|
7603
|
+
}
|
|
7604
|
+
function inactivePayload() {
|
|
7605
|
+
return { schemaVersion: CURRENT_STATUS_SCHEMA_VERSION, sessionActive: false, source: "hook" };
|
|
7606
|
+
}
|
|
7607
|
+
function activePayload(session) {
|
|
7608
|
+
return {
|
|
7609
|
+
schemaVersion: CURRENT_STATUS_SCHEMA_VERSION,
|
|
7610
|
+
sessionActive: true,
|
|
7611
|
+
sessionId: session.sessionId,
|
|
7612
|
+
state: session.state,
|
|
7613
|
+
ticket: session.ticket?.id ?? null,
|
|
7614
|
+
ticketTitle: session.ticket?.title ?? null,
|
|
7615
|
+
risk: session.ticket?.risk ?? null,
|
|
7616
|
+
claudeStatus: deriveClaudeStatus(session.state, session.waitingForRetry),
|
|
7617
|
+
observedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7618
|
+
lastGuideCall: session.lastGuideCall ?? null,
|
|
7619
|
+
completedThisSession: session.completedTickets?.map((t) => t.id) ?? [],
|
|
7620
|
+
contextPressure: session.contextPressure?.level ?? "unknown",
|
|
7621
|
+
branch: session.git?.branch ?? null,
|
|
7622
|
+
source: "hook"
|
|
7623
|
+
};
|
|
7624
|
+
}
|
|
7625
|
+
function ensureGitignore(root) {
|
|
7626
|
+
const gitignorePath = join14(root, ".story", ".gitignore");
|
|
7627
|
+
let existing = "";
|
|
7628
|
+
try {
|
|
7629
|
+
existing = readFileSync3(gitignorePath, "utf-8");
|
|
7630
|
+
} catch {
|
|
7631
|
+
}
|
|
7632
|
+
const lines = existing.split("\n").map((l) => l.trim());
|
|
7633
|
+
const missing = STORY_GITIGNORE_ENTRIES.filter((e) => !lines.includes(e));
|
|
7634
|
+
if (missing.length === 0) return;
|
|
7635
|
+
let content = existing;
|
|
7636
|
+
if (content.length > 0 && !content.endsWith("\n")) content += "\n";
|
|
7637
|
+
content += missing.join("\n") + "\n";
|
|
7638
|
+
try {
|
|
7639
|
+
writeFileSync3(gitignorePath, content, "utf-8");
|
|
7640
|
+
} catch {
|
|
7641
|
+
}
|
|
7642
|
+
}
|
|
7643
|
+
function writeStatus(root, payload) {
|
|
7644
|
+
ensureGitignore(root);
|
|
7645
|
+
const statusPath = join14(root, ".story", "status.json");
|
|
7646
|
+
const content = JSON.stringify(payload, null, 2) + "\n";
|
|
7647
|
+
atomicWriteSync(statusPath, content);
|
|
7648
|
+
}
|
|
7649
|
+
async function handleHookStatus() {
|
|
7650
|
+
try {
|
|
7651
|
+
if (process.stdin.isTTY) {
|
|
7652
|
+
const root2 = discoverProjectRoot();
|
|
7653
|
+
if (root2) {
|
|
7654
|
+
const session2 = findActiveSessionMinimal(root2);
|
|
7655
|
+
const payload2 = session2 ? activePayload(session2) : inactivePayload();
|
|
7656
|
+
writeStatus(root2, payload2);
|
|
7657
|
+
}
|
|
7658
|
+
process.exit(0);
|
|
7659
|
+
}
|
|
7660
|
+
const raw = await readStdinSilent();
|
|
7661
|
+
if (raw === null || raw === "") {
|
|
7662
|
+
process.exit(0);
|
|
7663
|
+
}
|
|
7664
|
+
let input;
|
|
7665
|
+
try {
|
|
7666
|
+
input = JSON.parse(raw);
|
|
7667
|
+
} catch {
|
|
7668
|
+
process.exit(0);
|
|
7669
|
+
}
|
|
7670
|
+
if (input.stop_hook_active === true) {
|
|
7671
|
+
process.exit(0);
|
|
7672
|
+
}
|
|
7673
|
+
const cwd = input.cwd;
|
|
7674
|
+
if (typeof cwd !== "string" || !cwd) {
|
|
7675
|
+
process.exit(0);
|
|
7676
|
+
}
|
|
7677
|
+
const root = discoverProjectRoot(cwd);
|
|
7678
|
+
if (!root) {
|
|
7679
|
+
process.exit(0);
|
|
7680
|
+
}
|
|
7681
|
+
const session = findActiveSessionMinimal(root);
|
|
7682
|
+
const payload = session ? activePayload(session) : inactivePayload();
|
|
7683
|
+
writeStatus(root, payload);
|
|
7684
|
+
} catch {
|
|
7685
|
+
}
|
|
7686
|
+
process.exit(0);
|
|
7687
|
+
}
|
|
7688
|
+
var init_hook_status = __esm({
|
|
7689
|
+
"src/cli/commands/hook-status.ts"() {
|
|
7690
|
+
"use strict";
|
|
7691
|
+
init_esm_shims();
|
|
7692
|
+
init_project_root_discovery();
|
|
7693
|
+
init_init();
|
|
7694
|
+
init_session_types();
|
|
7695
|
+
init_session();
|
|
5982
7696
|
}
|
|
5983
7697
|
});
|
|
5984
7698
|
|
|
@@ -5988,6 +7702,7 @@ __export(register_exports, {
|
|
|
5988
7702
|
registerBlockerCommand: () => registerBlockerCommand,
|
|
5989
7703
|
registerExportCommand: () => registerExportCommand,
|
|
5990
7704
|
registerHandoverCommand: () => registerHandoverCommand,
|
|
7705
|
+
registerHookStatusCommand: () => registerHookStatusCommand,
|
|
5991
7706
|
registerInitCommand: () => registerInitCommand,
|
|
5992
7707
|
registerIssueCommand: () => registerIssueCommand,
|
|
5993
7708
|
registerNoteCommand: () => registerNoteCommand,
|
|
@@ -7530,7 +9245,7 @@ function registerSetupSkillCommand(yargs) {
|
|
|
7530
9245
|
(y) => y.option("skip-hooks", {
|
|
7531
9246
|
type: "boolean",
|
|
7532
9247
|
default: false,
|
|
7533
|
-
description: "Skip PreCompact
|
|
9248
|
+
description: "Skip hook registration (PreCompact + Stop)"
|
|
7534
9249
|
}),
|
|
7535
9250
|
async (argv) => {
|
|
7536
9251
|
const { handleSetupSkill: handleSetupSkill2 } = await Promise.resolve().then(() => (init_setup_skill(), setup_skill_exports));
|
|
@@ -7538,6 +9253,18 @@ function registerSetupSkillCommand(yargs) {
|
|
|
7538
9253
|
}
|
|
7539
9254
|
);
|
|
7540
9255
|
}
|
|
9256
|
+
function registerHookStatusCommand(yargs) {
|
|
9257
|
+
return yargs.command(
|
|
9258
|
+
"hook-status",
|
|
9259
|
+
false,
|
|
9260
|
+
// hidden — machine-facing, not shown in --help
|
|
9261
|
+
(y) => y,
|
|
9262
|
+
async () => {
|
|
9263
|
+
const { handleHookStatus: handleHookStatus2 } = await Promise.resolve().then(() => (init_hook_status(), hook_status_exports));
|
|
9264
|
+
await handleHookStatus2();
|
|
9265
|
+
}
|
|
9266
|
+
);
|
|
9267
|
+
}
|
|
7541
9268
|
var init_register = __esm({
|
|
7542
9269
|
"src/cli/register.ts"() {
|
|
7543
9270
|
"use strict";
|
|
@@ -7591,9 +9318,10 @@ async function runCli() {
|
|
|
7591
9318
|
registerRecommendCommand: registerRecommendCommand2,
|
|
7592
9319
|
registerReferenceCommand: registerReferenceCommand2,
|
|
7593
9320
|
registerSelftestCommand: registerSelftestCommand2,
|
|
7594
|
-
registerSetupSkillCommand: registerSetupSkillCommand2
|
|
9321
|
+
registerSetupSkillCommand: registerSetupSkillCommand2,
|
|
9322
|
+
registerHookStatusCommand: registerHookStatusCommand2
|
|
7595
9323
|
} = await Promise.resolve().then(() => (init_register(), register_exports));
|
|
7596
|
-
const version2 = "0.1.
|
|
9324
|
+
const version2 = "0.1.13";
|
|
7597
9325
|
class HandledError extends Error {
|
|
7598
9326
|
constructor() {
|
|
7599
9327
|
super("HANDLED_ERROR");
|
|
@@ -7631,6 +9359,7 @@ async function runCli() {
|
|
|
7631
9359
|
cli = registerReferenceCommand2(cli);
|
|
7632
9360
|
cli = registerSelftestCommand2(cli);
|
|
7633
9361
|
cli = registerSetupSkillCommand2(cli);
|
|
9362
|
+
cli = registerHookStatusCommand2(cli);
|
|
7634
9363
|
function handleUnexpectedError(err) {
|
|
7635
9364
|
if (err instanceof HandledError) return;
|
|
7636
9365
|
const message = err instanceof Error ? err.message : String(err);
|