@wrongstack/cli 0.296.3 → 0.297.0
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/boot/dispatch-webui.d.ts +7 -0
- package/dist/boot/dispatch-webui.d.ts.map +1 -1
- package/dist/boot/launch-banner.d.ts +22 -0
- package/dist/boot/launch-banner.d.ts.map +1 -0
- package/dist/boot/launch-menu.d.ts.map +1 -1
- package/dist/boot/tui-settings-adapter.d.ts.map +1 -1
- package/dist/{chunk-OI7GWJDS.js → chunk-7JABKXET.js} +372 -10
- package/dist/chunk-7JABKXET.js.map +7 -0
- package/dist/{chunk-WY7KKOUK.js → chunk-JW75HY4F.js} +31 -10
- package/dist/chunk-JW75HY4F.js.map +7 -0
- package/dist/{chunk-Z7YGASQP.js → chunk-QZKWRNIP.js} +19 -18
- package/dist/chunk-QZKWRNIP.js.map +7 -0
- package/dist/{chunk-BQCAJ7FI.js → chunk-XTIXVJXP.js} +16 -3
- package/dist/chunk-XTIXVJXP.js.map +7 -0
- package/dist/{cli-main-RTY35OPD.js → cli-main-GOY5YGVL.js} +25199 -25112
- package/dist/cli-main-GOY5YGVL.js.map +7 -0
- package/dist/cli-main-helpers.d.ts +1 -1
- package/dist/cli-main-helpers.d.ts.map +1 -1
- package/dist/cli-main.d.ts.map +1 -1
- package/dist/{execution-O7I5DYE6.js → execution-A4PGK4XV.js} +39 -9
- package/dist/execution-A4PGK4XV.js.map +7 -0
- package/dist/execution.d.ts.map +1 -1
- package/dist/fleet-statusline.d.ts.map +1 -1
- package/dist/goal-host.d.ts +1 -1
- package/dist/goal-host.d.ts.map +1 -1
- package/dist/{hq-JUYKUNJX.js → hq-GBPGXIR5.js} +2 -2
- package/dist/hq-publisher.d.ts +3 -2
- package/dist/hq-publisher.d.ts.map +1 -1
- package/dist/hq-server/types.d.ts +8 -0
- package/dist/hq-server/types.d.ts.map +1 -1
- package/dist/hq-server/ws.d.ts +61 -2
- package/dist/hq-server/ws.d.ts.map +1 -1
- package/dist/{hq-server-6AQ3BXZA.js → hq-server-YQUUMTPU.js} +2 -2
- package/dist/index.js +293 -26
- package/dist/index.js.map +4 -4
- package/dist/kanban-hq-sync.d.ts +13 -1
- package/dist/kanban-hq-sync.d.ts.map +1 -1
- package/dist/live-settings-input.d.ts +9 -0
- package/dist/live-settings-input.d.ts.map +1 -1
- package/dist/{mailbox-serve-2UWHEN3M.js → mailbox-serve-DBOOOOWZ.js} +50 -8
- package/dist/mailbox-serve-DBOOOOWZ.js.map +7 -0
- package/dist/{sdd-runtime-JG4EEKL6.js → sdd-runtime-AIJCJGWC.js} +2 -2
- package/dist/services/sdd/state.d.ts +3 -3
- package/dist/services/sdd/state.d.ts.map +1 -1
- package/dist/services/sdd/task-manager.d.ts.map +1 -1
- package/dist/session-stats.d.ts.map +1 -1
- package/dist/settings-menu.d.ts.map +1 -1
- package/dist/slash-commands/command-context.d.ts +2 -0
- package/dist/slash-commands/command-context.d.ts.map +1 -1
- package/dist/slash-commands/goal.d.ts +1 -1
- package/dist/slash-commands/goal.d.ts.map +1 -1
- package/dist/slash-commands/sdd.d.ts.map +1 -1
- package/dist/webui-server/kanban-host-adapter.d.ts +1 -1
- package/dist/webui-server/kanban-host-adapter.d.ts.map +1 -1
- package/dist/webui-server/session-start-payload.d.ts +4 -0
- package/dist/webui-server/session-start-payload.d.ts.map +1 -1
- package/dist/{webui-server-ZF5KVGC5.js → webui-server-XKFUOKMJ.js} +155 -132
- package/dist/webui-server-XKFUOKMJ.js.map +7 -0
- package/dist/webui-server-options.d.ts +7 -0
- package/dist/webui-server-options.d.ts.map +1 -1
- package/dist/webui-server.d.ts.map +1 -1
- package/dist/wiring/command-host-state.d.ts.map +1 -1
- package/dist/wiring/hq-telemetry.d.ts +4 -3
- package/dist/wiring/hq-telemetry.d.ts.map +1 -1
- package/dist/wiring/lifecycle-plugins.d.ts +5 -8
- package/dist/wiring/lifecycle-plugins.d.ts.map +1 -1
- package/dist/wiring/metrics.d.ts +1 -1
- package/dist/wiring/metrics.d.ts.map +1 -1
- package/dist/wiring/runtime-lifecycle-deps.d.ts.map +1 -1
- package/dist/wiring/sage.d.ts.map +1 -1
- package/dist/wiring/sdd-handlers.d.ts.map +1 -1
- package/package.json +22 -22
- package/dist/chunk-BQCAJ7FI.js.map +0 -7
- package/dist/chunk-OI7GWJDS.js.map +0 -7
- package/dist/chunk-WY7KKOUK.js.map +0 -7
- package/dist/chunk-Z7YGASQP.js.map +0 -7
- package/dist/cli-main-RTY35OPD.js.map +0 -7
- package/dist/execution-O7I5DYE6.js.map +0 -7
- package/dist/mailbox-serve-2UWHEN3M.js.map +0 -7
- package/dist/webui-server-ZF5KVGC5.js.map +0 -7
- /package/dist/{hq-JUYKUNJX.js.map → hq-GBPGXIR5.js.map} +0 -0
- /package/dist/{hq-server-6AQ3BXZA.js.map → hq-server-YQUUMTPU.js.map} +0 -0
- /package/dist/{sdd-runtime-JG4EEKL6.js.map → sdd-runtime-AIJCJGWC.js.map} +0 -0
|
@@ -24,7 +24,7 @@ function warnSyncFailure(error) {
|
|
|
24
24
|
{ code: "WRONGSTACK_HQ_KANBAN_SYNC_FAILED" }
|
|
25
25
|
);
|
|
26
26
|
}
|
|
27
|
-
function createKanbanHqSync(projectRoot, projectId = deriveHqProjectId(projectRoot)) {
|
|
27
|
+
function createKanbanHqSync(projectRoot, projectId = deriveHqProjectId(projectRoot), testHooks = {}) {
|
|
28
28
|
let publisher;
|
|
29
29
|
let unsubscribeDaemon;
|
|
30
30
|
let daemonConnectionCount = 0;
|
|
@@ -34,6 +34,9 @@ function createKanbanHqSync(projectRoot, projectId = deriveHqProjectId(projectRo
|
|
|
34
34
|
let applyingRemote = false;
|
|
35
35
|
let stopped = false;
|
|
36
36
|
let operationChain = Promise.resolve();
|
|
37
|
+
let localPublishActive = false;
|
|
38
|
+
let localPublishRuns = 0;
|
|
39
|
+
let coalescedLocalRefreshes = 0;
|
|
37
40
|
let pendingRemote = null;
|
|
38
41
|
let remoteApplyQueued = false;
|
|
39
42
|
const runExclusive = (operation) => {
|
|
@@ -53,16 +56,23 @@ function createKanbanHqSync(projectRoot, projectId = deriveHqProjectId(projectRo
|
|
|
53
56
|
return result;
|
|
54
57
|
};
|
|
55
58
|
const scheduleNext = () => {
|
|
56
|
-
if (stopped || timer !== void 0 || applyingRemote) return;
|
|
59
|
+
if (stopped || timer !== void 0 || localPublishActive || applyingRemote) return;
|
|
57
60
|
timer = setTimeout(() => {
|
|
58
61
|
timer = void 0;
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
62
|
+
if (stopped || localPublishActive) return;
|
|
63
|
+
localPublishActive = true;
|
|
64
|
+
void runExclusive(async () => {
|
|
65
|
+
const rescan = fullRescanPending;
|
|
66
|
+
const dirtyBoardIds = [...pendingBoardIds];
|
|
67
|
+
fullRescanPending = false;
|
|
68
|
+
pendingBoardIds.clear();
|
|
69
|
+
localPublishRuns++;
|
|
70
|
+
await testHooks.beforeLocalPublish?.();
|
|
71
|
+
await publishLocal(false, rescan ? void 0 : dirtyBoardIds);
|
|
72
|
+
}).catch(warnSyncFailure).finally(() => {
|
|
73
|
+
localPublishActive = false;
|
|
74
|
+
if (!stopped && (fullRescanPending || pendingBoardIds.size > 0)) scheduleNext();
|
|
75
|
+
});
|
|
66
76
|
}, 100);
|
|
67
77
|
timer.unref?.();
|
|
68
78
|
};
|
|
@@ -70,6 +80,7 @@ function createKanbanHqSync(projectRoot, projectId = deriveHqProjectId(projectRo
|
|
|
70
80
|
if (stopped || applyingRemote || publisher === void 0) return;
|
|
71
81
|
if (boardId === null) fullRescanPending = true;
|
|
72
82
|
else pendingBoardIds.add(boardId);
|
|
83
|
+
if (timer !== void 0 || localPublishActive) coalescedLocalRefreshes++;
|
|
73
84
|
if (timer !== void 0) clearTimeout(timer);
|
|
74
85
|
timer = void 0;
|
|
75
86
|
scheduleNext();
|
|
@@ -238,6 +249,15 @@ function createKanbanHqSync(projectRoot, projectId = deriveHqProjectId(projectRo
|
|
|
238
249
|
attachPublisher,
|
|
239
250
|
handleRemote,
|
|
240
251
|
refresh: (boardId) => schedulePublish(boardId ?? null),
|
|
252
|
+
getStats: () => ({
|
|
253
|
+
localPublishActive,
|
|
254
|
+
pendingBoardIds: pendingBoardIds.size,
|
|
255
|
+
fullRescanPending,
|
|
256
|
+
remoteApplyQueued,
|
|
257
|
+
pendingRemoteBoards: pendingRemote?.boards.length ?? 0,
|
|
258
|
+
localPublishRuns,
|
|
259
|
+
coalescedLocalRefreshes
|
|
260
|
+
}),
|
|
241
261
|
stop: () => {
|
|
242
262
|
stopped = true;
|
|
243
263
|
if (timer !== void 0) clearTimeout(timer);
|
|
@@ -441,6 +461,7 @@ function startCliHqConnection(options) {
|
|
|
441
461
|
}
|
|
442
462
|
return {
|
|
443
463
|
getPublisher: () => publisher,
|
|
464
|
+
getKanbanSyncStats: () => kanbanSync?.getStats(),
|
|
444
465
|
stop: () => {
|
|
445
466
|
if (timer !== void 0) {
|
|
446
467
|
clearInterval(timer);
|
|
@@ -457,4 +478,4 @@ function startCliHqConnection(options) {
|
|
|
457
478
|
export {
|
|
458
479
|
startCliHqConnection
|
|
459
480
|
};
|
|
460
|
-
//# sourceMappingURL=chunk-
|
|
481
|
+
//# sourceMappingURL=chunk-JW75HY4F.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/hq-publisher.ts", "../src/kanban-hq-sync.ts"],
|
|
4
|
+
"sourcesContent": ["import type { CreateHqPublisherOptions, HqPublisher, HqSocketLike } from '@wrongstack/core/hq';\nimport { createHqPublisherFromEnv, resolveHqConfig } from '@wrongstack/core/hq';\nimport { WebSocket } from 'ws';\nimport { createKanbanHqSync, type KanbanHqSyncStats } from './kanban-hq-sync.js';\n\ntype CliHqPublisherOptions = Omit<CreateHqPublisherOptions, 'socketFactory'> & {\n socketFactory?: CreateHqPublisherOptions['socketFactory'];\n};\n\nexport interface CliHqConnection {\n getPublisher(): HqPublisher | undefined;\n getKanbanSyncStats(): KanbanHqSyncStats | undefined;\n stop(): void;\n}\n\nexport interface CliHqConnectionOptions extends CliHqPublisherOptions {\n onConnect?: ((publisher: HqPublisher) => void) | undefined;\n retryIntervalMs?: number | undefined;\n}\n\nfunction nodeWsSocketFactory(url: string): HqSocketLike {\n return new WebSocket(url) as unknown as HqSocketLike;\n}\n\nexport function createCliHqPublisher(options: CliHqPublisherOptions): HqPublisher | undefined {\n return createHqPublisherFromEnv({\n ...options,\n socketFactory: options.socketFactory ?? nodeWsSocketFactory,\n });\n}\n\nfunction hqExplicitlyDisabled(options: CliHqPublisherOptions): boolean {\n const envEnabled = process.env['WRONGSTACK_HQ_ENABLED']?.trim();\n if (envEnabled !== undefined && envEnabled.length > 0) return envEnabled === '0';\n if (options.config?.enabled !== undefined) return options.config.enabled === false;\n return options.appConfig?.hq?.enabled === false;\n}\n\nfunction resolvedHqConnectionKey(options: CliHqPublisherOptions): string | undefined {\n if (options.config !== undefined) {\n return options.config.enabled === false\n ? undefined\n : `${options.config.url}\\n${options.config.token ?? ''}`;\n }\n const config = resolveHqConfig({ config: options.appConfig?.hq });\n return config === undefined ? undefined : `${config.url}\\n${config.token ?? ''}`;\n}\n\nexport function startCliHqConnection(options: CliHqConnectionOptions): CliHqConnection {\n const shouldOwnKanbanSync = options.onKanbanSnapshot === undefined;\n let kanbanSync: ReturnType<typeof createKanbanHqSync> | undefined;\n const publisherOptions: CliHqConnectionOptions = shouldOwnKanbanSync\n ? {\n ...options,\n onKanbanSnapshot: (snapshot) => kanbanSync?.handleRemote(snapshot),\n }\n : options;\n let publisher: HqPublisher | undefined;\n let publisherKey: string | undefined;\n let timer: ReturnType<typeof setInterval> | undefined;\n\n const tryConnect = (): void => {\n const nextKey = resolvedHqConnectionKey(options);\n if (nextKey === undefined) return;\n if (publisher !== undefined && publisherKey === nextKey) return;\n\n publisher?.close();\n publisher = undefined;\n publisherKey = undefined;\n\n const next = createCliHqPublisher(publisherOptions);\n if (next === undefined) return;\n publisher = next;\n publisherKey = nextKey;\n if (shouldOwnKanbanSync) {\n kanbanSync?.stop();\n kanbanSync = createKanbanHqSync(options.projectRoot, next.project.projectId);\n }\n next.connect();\n if (kanbanSync !== undefined) {\n kanbanSync.attachPublisher(next).catch((error: unknown) => {\n process.emitWarning(\n `WrongStack kanban HQ sync attach failed (best-effort, retried on reconnect): ${\n error instanceof Error ? error.message : String(error)\n }`,\n { code: 'WRONGSTACK_HQ_KANBAN_SYNC_FAILED' },\n );\n });\n }\n options.onConnect?.(next);\n };\n\n tryConnect();\n if (!hqExplicitlyDisabled(options)) {\n timer = setInterval(tryConnect, options.retryIntervalMs ?? 2_500);\n timer.unref?.();\n }\n\n return {\n getPublisher: () => publisher,\n getKanbanSyncStats: () => kanbanSync?.getStats(),\n stop: () => {\n if (timer !== undefined) {\n clearInterval(timer);\n timer = undefined;\n }\n kanbanSync?.stop();\n publisher?.close();\n publisher = undefined;\n publisherKey = undefined;\n },\n };\n}\n", "import type {\n HqKanbanBoardRecord,\n HqKanbanSnapshotPayload,\n HqKanbanTombstone,\n HqPublisher,\n} from '@wrongstack/core/hq';\nimport { deriveHqProjectId } from '@wrongstack/core/hq';\nimport {\n bridgeKanbanSupervisor,\n deleteBoard,\n type KanbanBoard,\n listBoardIds,\n readBoard,\n readKanbanMetadata,\n writeBoard,\n writeKanbanMetadata,\n} from '@wrongstack/kanban';\n\ninterface LocalSyncState {\n boards: Record<string, { revision: number; updatedAt: string }>;\n tombstones: Record<string, HqKanbanTombstone>;\n}\n\nexport interface KanbanHqSync {\n attachPublisher(publisher: HqPublisher): Promise<void>;\n handleRemote(snapshot: HqKanbanSnapshotPayload): Promise<void>;\n /** Explicit refresh seam for hosts/tests; daemon events call the same path. */\n refresh(boardId?: string): void;\n getStats(): KanbanHqSyncStats;\n stop(): void;\n}\n\nexport interface KanbanHqSyncStats {\n localPublishActive: boolean;\n pendingBoardIds: number;\n fullRescanPending: boolean;\n remoteApplyQueued: boolean;\n pendingRemoteBoards: number;\n localPublishRuns: number;\n coalescedLocalRefreshes: number;\n}\n\nconst SYNC_STATE_KEY = 'hq-sync-state-v1';\n// The HQ WebSocket server deliberately caps inbound frames at 1 MiB. Keep\n// project-state payloads comfortably below that limit so the event envelope,\n// UTF-8 expansion, and future protocol metadata still have headroom.\nconst MAX_KANBAN_SNAPSHOT_PAYLOAD_BYTES = 512 * 1024;\nconst KANBAN_TOMBSTONE_RETENTION_MS = 30 * 24 * 60 * 60 * 1000;\n\n// Cap the number of boards read per publish cycle so a large project doesn't\n// load hundreds of board payloads in a single burst. Remaining boards spill to\n// the next cycle.\nconst MAX_BOARDS_PER_PUBLISH = 50;\n\n// Kanban\u2194HQ sync is best-effort telemetry: every detached promise in this\n// module must land here instead of surfacing as an unhandled rejection (which\n// kills the host process on Node \u226515). Failed publishes self-heal \u2014 the state\n// fingerprint stays unwritten, so the next watcher event or reconnect\n// full-snapshot re-detects the same diff.\nfunction warnSyncFailure(error: unknown): void {\n process.emitWarning(\n `WrongStack kanban HQ sync failed (best-effort, will retry on next change): ${\n error instanceof Error ? error.message : String(error)\n }`,\n { code: 'WRONGSTACK_HQ_KANBAN_SYNC_FAILED' },\n );\n}\n\nexport function createKanbanHqSync(\n projectRoot: string,\n projectId = deriveHqProjectId(projectRoot),\n testHooks: { beforeLocalPublish?: (() => Promise<void>) | undefined } = {},\n): KanbanHqSync {\n let publisher: HqPublisher | undefined;\n let unsubscribeDaemon: (() => void) | undefined;\n let daemonConnectionCount = 0;\n let timer: ReturnType<typeof setTimeout> | undefined;\n const pendingBoardIds = new Set<string>();\n let fullRescanPending = false;\n let applyingRemote = false;\n let stopped = false;\n let operationChain: Promise<void> = Promise.resolve();\n let localPublishActive = false;\n let localPublishRuns = 0;\n let coalescedLocalRefreshes = 0;\n // Remote snapshots waiting to be applied, coalesced per boardId (latest\n // revision wins). Applying a snapshot does one IPC read/write per board;\n // when snapshots arrive faster than the owner can commit them, queueing each one\n // individually on `operationChain` retains every payload in memory \u2014 an\n // unbounded queue that has driven long fleet sessions into OOM. Coalescing\n // bounds pending memory to one record per distinct board.\n let pendingRemote: HqKanbanSnapshotPayload | null = null;\n let remoteApplyQueued = false;\n\n const runExclusive = <T>(operation: () => Promise<T>): Promise<T> => {\n let resolveResult!: (value: T | PromiseLike<T>) => void;\n let rejectResult!: (reason?: unknown) => void;\n const result = new Promise<T>((resolve, reject) => {\n resolveResult = resolve;\n rejectResult = reject;\n });\n operationChain = operationChain.then(async () => {\n try {\n resolveResult(await operation());\n } catch (error) {\n rejectResult(error);\n }\n });\n return result;\n };\n\n // Schedule the next publish cycle with a fixed debounce. The caller\n // (schedulePublish) already collapses rapid events into one batch, and\n // runExclusive serializes execution \u2014 no Date.now()-based interval is\n // needed because the 100ms debounce plus serialization chain already\n // prevents back-to-back publish bursts.\n const scheduleNext = (): void => {\n if (stopped || timer !== undefined || localPublishActive || applyingRemote) return;\n timer = setTimeout(() => {\n timer = undefined;\n if (stopped || localPublishActive) return;\n localPublishActive = true;\n void runExclusive(async () => {\n const rescan = fullRescanPending;\n const dirtyBoardIds = [...pendingBoardIds];\n fullRescanPending = false;\n pendingBoardIds.clear();\n localPublishRuns++;\n await testHooks.beforeLocalPublish?.();\n await publishLocal(false, rescan ? undefined : dirtyBoardIds);\n })\n .catch(warnSyncFailure)\n .finally(() => {\n localPublishActive = false;\n if (!stopped && (fullRescanPending || pendingBoardIds.size > 0)) scheduleNext();\n });\n }, 100);\n timer.unref?.();\n };\n\n const schedulePublish = (boardId: string | null): void => {\n if (stopped || applyingRemote || publisher === undefined) return;\n if (boardId === null) fullRescanPending = true;\n else pendingBoardIds.add(boardId);\n if (timer !== undefined || localPublishActive) coalescedLocalRefreshes++;\n if (timer !== undefined) clearTimeout(timer);\n timer = undefined;\n scheduleNext();\n };\n\n const publishLocal = async (\n fullSnapshot: boolean,\n dirtyBoardIds?: readonly string[],\n ): Promise<void> => {\n const target = publisher;\n if (target === undefined || stopped) return;\n const state = await readState(projectRoot);\n const prunedTombstones = pruneExpiredTombstones(state, Date.now());\n let stateChanged = fullSnapshot || prunedTombstones;\n const boards: HqKanbanBoardRecord[] = [];\n const tombstones: HqKanbanTombstone[] = [];\n const fullScan = fullSnapshot || dirtyBoardIds === undefined;\n const allBoardIds: readonly string[] = fullScan\n ? await listBoardIds(projectRoot)\n : dirtyBoardIds;\n let boardIds = allBoardIds;\n\n // Batch limit: only cap true delta publishes, never a full scan (initial\n // snapshot OR full rescan). When fullScan is true, ALL boards must be\n // loaded into `present` so the deletion-detection loop below does NOT\n // false-tombstone the boards this cycle didn't reach.\n if (!fullScan && boardIds.length > MAX_BOARDS_PER_PUBLISH) {\n for (const id of boardIds.slice(MAX_BOARDS_PER_PUBLISH)) {\n pendingBoardIds.add(id);\n }\n scheduleNext();\n boardIds = boardIds.slice(0, MAX_BOARDS_PER_PUBLISH);\n }\n\n const present = new Set<string>();\n const now = new Date().toISOString();\n for (const boardId of boardIds) {\n const board = await readBoard(projectRoot, boardId);\n if (board === null) {\n const known = state.boards[boardId];\n if (known !== undefined && state.tombstones[boardId] === undefined) {\n const tombstone = {\n boardId,\n revision: known.revision + 1,\n deletedAt: now,\n };\n state.tombstones[boardId] = tombstone;\n tombstones.push(tombstone);\n stateChanged = true;\n }\n continue;\n }\n present.add(boardId);\n const record = boardRecord(board);\n const known = state.boards[boardId];\n const hadTombstone = state.tombstones[boardId] !== undefined;\n if (\n fullSnapshot ||\n hadTombstone ||\n known === undefined ||\n compareVersions(known.revision, known.updatedAt, record.revision, record.updatedAt) !== 0\n ) {\n boards.push(record);\n }\n if (\n known === undefined ||\n known.revision !== record.revision ||\n known.updatedAt !== record.updatedAt ||\n hadTombstone\n ) {\n stateChanged = true;\n }\n state.boards[boardId] = { revision: record.revision, updatedAt: record.updatedAt };\n delete state.tombstones[boardId];\n }\n if (fullScan) {\n for (const [boardId, known] of Object.entries(state.boards)) {\n if (\n present.has(boardId) ||\n state.tombstones[boardId] !== undefined ||\n isExpiredVersion(known.updatedAt, Date.now())\n ) {\n continue;\n }\n const tombstone = {\n boardId,\n revision: known.revision + 1,\n deletedAt: now,\n };\n state.tombstones[boardId] = tombstone;\n tombstones.push(tombstone);\n stateChanged = true;\n }\n }\n if (stateChanged) await writeState(projectRoot, state);\n\n // A filesystem event caused by applying an HQ snapshot often arrives after\n // `applyingRemote` has been cleared. The persisted state fingerprint above\n // lets that event collapse to an empty delta instead of echoing the entire\n // project back to HQ and starting an N-client broadcast storm.\n if (!fullSnapshot && boards.length === 0 && tombstones.length === 0) return;\n\n for (const payload of chunkSnapshotPayload(\n projectId,\n now,\n boards,\n fullSnapshot ? Object.values(state.tombstones) : tombstones,\n )) {\n target.publishEvent({\n type: 'kanban.snapshot',\n payload,\n maxSummaryLength: 100_000,\n });\n }\n };\n\n const attachPublisher = (next: HqPublisher): Promise<void> => {\n publisher = next;\n return runExclusive(async () => {\n if (\n unsubscribeDaemon === undefined &&\n (process.env['NODE_ENV'] !== 'test' || process.env['WRONGSTACK_KANBAN_FORCE_IPC'] === '1')\n ) {\n unsubscribeDaemon = bridgeKanbanSupervisor(\n projectRoot,\n (event) => {\n const data = event.data as { boardId?: string } | undefined;\n // Non-board-scoped events conservatively request a full rescan.\n schedulePublish(data?.boardId ?? null);\n },\n {\n autoReconnect: true,\n reconnectDelayMs: 1_000,\n // Events emitted while the daemon was down cannot be replayed.\n // Reconcile the authoritative snapshot after every connection.\n onConnected: () => {\n daemonConnectionCount++;\n if (daemonConnectionCount > 1) schedulePublish(null);\n },\n },\n );\n }\n await publishLocal(true);\n });\n };\n\n const applyRemote = async (snapshot: HqKanbanSnapshotPayload): Promise<void> => {\n if (snapshot.projectId !== projectId || stopped) return;\n applyingRemote = true;\n try {\n const state = await readState(projectRoot);\n for (const remote of snapshot.boards) {\n const local = await readBoard(projectRoot, remote.boardId);\n if (\n local !== null &&\n compareVersions(\n local.revision ?? 0,\n local.updatedAt,\n remote.revision,\n remote.updatedAt,\n ) >= 0\n ) {\n continue;\n }\n await writeBoard(projectRoot, remote.board as unknown as KanbanBoard);\n state.boards[remote.boardId] = {\n revision: remote.revision,\n updatedAt: remote.updatedAt,\n };\n delete state.tombstones[remote.boardId];\n }\n for (const tombstone of snapshot.tombstones) {\n const local = await readBoard(projectRoot, tombstone.boardId);\n if (\n local !== null &&\n compareVersions(\n local.revision ?? 0,\n local.updatedAt,\n tombstone.revision,\n tombstone.deletedAt,\n ) >= 0\n ) {\n continue;\n }\n if (local !== null) await deleteBoard(projectRoot, tombstone.boardId);\n state.boards[tombstone.boardId] = {\n revision: Math.max(0, tombstone.revision - 1),\n updatedAt: tombstone.deletedAt,\n };\n state.tombstones[tombstone.boardId] = tombstone;\n }\n await writeState(projectRoot, state);\n } finally {\n applyingRemote = false;\n }\n };\n\n const handleRemote = (snapshot: HqKanbanSnapshotPayload): Promise<void> => {\n if (snapshot.projectId !== projectId || stopped) return Promise.resolve();\n pendingRemote =\n pendingRemote === null ? snapshot : coalesceRemoteSnapshots(pendingRemote, snapshot);\n // At most one apply pass is ever queued: it drains whatever has been\n // coalesced by the time it runs, so bursts collapse instead of chaining.\n if (remoteApplyQueued) return Promise.resolve();\n remoteApplyQueued = true;\n return runExclusive(async () => {\n remoteApplyQueued = false;\n const batch = pendingRemote;\n pendingRemote = null;\n if (batch !== null) await applyRemote(batch);\n }).catch(warnSyncFailure);\n };\n\n return {\n attachPublisher,\n handleRemote,\n refresh: (boardId?: string) => schedulePublish(boardId ?? null),\n getStats: () => ({\n localPublishActive,\n pendingBoardIds: pendingBoardIds.size,\n fullRescanPending,\n remoteApplyQueued,\n pendingRemoteBoards: pendingRemote?.boards.length ?? 0,\n localPublishRuns,\n coalescedLocalRefreshes,\n }),\n stop: () => {\n stopped = true;\n if (timer !== undefined) clearTimeout(timer);\n pendingBoardIds.clear();\n fullRescanPending = false;\n pendingRemote = null;\n unsubscribeDaemon?.();\n unsubscribeDaemon = undefined;\n publisher = undefined;\n },\n };\n}\n\nfunction boardRecord(board: KanbanBoard): HqKanbanBoardRecord {\n return {\n boardId: board.id,\n revision: board.revision ?? 0,\n updatedAt: board.updatedAt,\n board: structuredClone(board) as unknown as Record<string, unknown>,\n };\n}\n\nfunction chunkSnapshotPayload(\n projectId: string,\n generatedAt: string,\n boards: HqKanbanBoardRecord[],\n tombstones: HqKanbanTombstone[],\n): HqKanbanSnapshotPayload[] {\n const chunks: HqKanbanSnapshotPayload[] = [];\n const emptyPayload = (): HqKanbanSnapshotPayload => ({\n projectId,\n generatedAt,\n boards: [],\n tombstones: [],\n });\n const emptyPayloadBytes = Buffer.byteLength(JSON.stringify(emptyPayload()), 'utf8');\n let currentBoards: HqKanbanBoardRecord[] = [];\n let currentTombstones: HqKanbanTombstone[] = [];\n let currentBytes = emptyPayloadBytes;\n\n const pushCurrent = (): void => {\n if (currentBoards.length === 0 && currentTombstones.length === 0) return;\n chunks.push({\n projectId,\n generatedAt,\n boards: currentBoards,\n tombstones: currentTombstones,\n });\n currentBoards = [];\n currentTombstones = [];\n currentBytes = emptyPayloadBytes;\n };\n\n const append = (\n kind: 'boards' | 'tombstones',\n item: HqKanbanBoardRecord | HqKanbanTombstone,\n ): void => {\n const itemBytes = Buffer.byteLength(JSON.stringify(item), 'utf8');\n const itemCount = kind === 'boards' ? currentBoards.length : currentTombstones.length;\n const additionalBytes = itemBytes + (itemCount > 0 ? 1 : 0);\n if (emptyPayloadBytes + itemBytes > MAX_KANBAN_SNAPSHOT_PAYLOAD_BYTES) {\n process.emitWarning(\n `Kanban ${kind === 'boards' ? 'board' : 'tombstone'} ${item.boardId} exceeds the ${MAX_KANBAN_SNAPSHOT_PAYLOAD_BYTES}-byte snapshot chunk target`,\n { code: 'WRONGSTACK_HQ_KANBAN_OVERSIZED_RECORD' },\n );\n }\n if (\n (currentBoards.length > 0 || currentTombstones.length > 0) &&\n currentBytes + additionalBytes > MAX_KANBAN_SNAPSHOT_PAYLOAD_BYTES\n ) {\n pushCurrent();\n }\n if (kind === 'boards') {\n currentBoards.push(item as HqKanbanBoardRecord);\n } else {\n currentTombstones.push(item as HqKanbanTombstone);\n }\n currentBytes +=\n itemBytes +\n (kind === 'boards'\n ? currentBoards.length > 1\n ? 1\n : 0\n : currentTombstones.length > 1\n ? 1\n : 0);\n };\n\n for (const board of boards) append('boards', board);\n for (const tombstone of tombstones) append('tombstones', tombstone);\n pushCurrent();\n\n // An empty snapshot is meaningful on first attach: it registers the project\n // state channel even when the project has no boards yet.\n return chunks.length > 0 ? chunks : [emptyPayload()];\n}\n\n/** Merge two pending remote snapshots into one, keeping \u2014 per boardId \u2014 the\n * record with the highest (revision, timestamp), whether board or tombstone.\n * Equivalent to applying both snapshots in arrival order when revisions are\n * monotonically increasing (which HQ guarantees in practice): the only\n * divergence is a lower-revision board arriving after a higher-revision\n * tombstone for the same boardId, where coalescing correctly keeps the\n * tombstone whereas naive sequential apply would resurrect the stale board. */\nfunction coalesceRemoteSnapshots(\n older: HqKanbanSnapshotPayload,\n newer: HqKanbanSnapshotPayload,\n): HqKanbanSnapshotPayload {\n type RemoteRecord =\n | { kind: 'board'; revision: number; time: string; record: HqKanbanBoardRecord }\n | { kind: 'tombstone'; revision: number; time: string; record: HqKanbanTombstone };\n const byBoardId = new Map<string, RemoteRecord>();\n const put = (candidate: RemoteRecord, boardId: string): void => {\n const existing = byBoardId.get(boardId);\n // On a full (revision, time) tie, prefer the newer-arriving record so a\n // same-timestamp content update in the second snapshot is not silently\n // discarded in favor of the older-arriving one.\n if (\n existing === undefined ||\n compareVersions(existing.revision, existing.time, candidate.revision, candidate.time) <= 0\n ) {\n byBoardId.set(boardId, candidate);\n }\n };\n for (const snapshot of [older, newer]) {\n for (const record of snapshot.boards) {\n put(\n { kind: 'board', revision: record.revision, time: record.updatedAt, record },\n record.boardId,\n );\n }\n for (const record of snapshot.tombstones) {\n put(\n { kind: 'tombstone', revision: record.revision, time: record.deletedAt, record },\n record.boardId,\n );\n }\n }\n const merged: HqKanbanSnapshotPayload = {\n projectId: newer.projectId,\n generatedAt: newer.generatedAt,\n boards: [],\n tombstones: [],\n };\n for (const entry of byBoardId.values()) {\n if (entry.kind === 'board') merged.boards.push(entry.record);\n else merged.tombstones.push(entry.record);\n }\n return merged;\n}\n\nfunction compareVersions(\n aRevision: number,\n aTime: string,\n bRevision: number,\n bTime: string,\n): number {\n if (aRevision !== bRevision) return aRevision - bRevision;\n return aTime.localeCompare(bTime);\n}\n\nfunction isExpiredVersion(timestamp: string, now: number): boolean {\n const parsed = Date.parse(timestamp);\n return !Number.isNaN(parsed) && parsed <= now - KANBAN_TOMBSTONE_RETENTION_MS;\n}\n\nfunction pruneExpiredTombstones(state: LocalSyncState, now: number): boolean {\n let changed = false;\n for (const [boardId, tombstone] of Object.entries(state.tombstones)) {\n if (!isExpiredVersion(tombstone.deletedAt, now)) continue;\n delete state.tombstones[boardId];\n delete state.boards[boardId];\n changed = true;\n }\n return changed;\n}\n\nasync function readState(projectRoot: string): Promise<LocalSyncState> {\n try {\n const raw = await readKanbanMetadata(projectRoot, SYNC_STATE_KEY);\n if (raw === null) return { boards: {}, tombstones: {} };\n const parsed = JSON.parse(raw) as Partial<LocalSyncState>;\n return { boards: parsed.boards ?? {}, tombstones: parsed.tombstones ?? {} };\n } catch {\n return { boards: {}, tombstones: {} };\n }\n}\n\nasync function writeState(projectRoot: string, state: LocalSyncState): Promise<void> {\n await writeKanbanMetadata(projectRoot, SYNC_STATE_KEY, JSON.stringify(state));\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["known"]
|
|
7
|
+
}
|
|
@@ -1,17 +1,4 @@
|
|
|
1
1
|
|
|
2
|
-
// src/tui-thinking-word.ts
|
|
3
|
-
var DEFAULT_TUI_THINKING_WORD = "thinking";
|
|
4
|
-
var MAX_TUI_THINKING_WORD_LENGTH = 16;
|
|
5
|
-
function normalizeTuiThinkingWord(value) {
|
|
6
|
-
if (typeof value !== "string") return DEFAULT_TUI_THINKING_WORD;
|
|
7
|
-
const word = value.trim();
|
|
8
|
-
if (word.length === 0 || word.length > MAX_TUI_THINKING_WORD_LENGTH) {
|
|
9
|
-
return DEFAULT_TUI_THINKING_WORD;
|
|
10
|
-
}
|
|
11
|
-
if (!/^[\p{L}\p{N}_-]+$/u.test(word)) return DEFAULT_TUI_THINKING_WORD;
|
|
12
|
-
return word;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
2
|
// src/settings-menu.ts
|
|
16
3
|
import * as fs from "node:fs/promises";
|
|
17
4
|
import * as path from "node:path";
|
|
@@ -107,7 +94,8 @@ var PROJECT_SAFE_FIELDS = /* @__PURE__ */ new Set([
|
|
|
107
94
|
"launch",
|
|
108
95
|
"circuitBreaker",
|
|
109
96
|
"adaptiveConcurrency",
|
|
110
|
-
"modelRuntime"
|
|
97
|
+
"modelRuntime",
|
|
98
|
+
"Sage"
|
|
111
99
|
]);
|
|
112
100
|
function filterSafeForProject(cfg) {
|
|
113
101
|
const out = {};
|
|
@@ -294,9 +282,20 @@ function clearSuggestions() {
|
|
|
294
282
|
sharedAutoSuggestions = [];
|
|
295
283
|
}
|
|
296
284
|
|
|
285
|
+
// src/tui-thinking-word.ts
|
|
286
|
+
var DEFAULT_TUI_THINKING_WORD = "thinking";
|
|
287
|
+
var MAX_TUI_THINKING_WORD_LENGTH = 16;
|
|
288
|
+
function normalizeTuiThinkingWord(value) {
|
|
289
|
+
if (typeof value !== "string") return DEFAULT_TUI_THINKING_WORD;
|
|
290
|
+
const word = value.trim();
|
|
291
|
+
if (word.length === 0 || word.length > MAX_TUI_THINKING_WORD_LENGTH) {
|
|
292
|
+
return DEFAULT_TUI_THINKING_WORD;
|
|
293
|
+
}
|
|
294
|
+
if (!/^[\p{L}\p{N}_-]+$/u.test(word)) return DEFAULT_TUI_THINKING_WORD;
|
|
295
|
+
return word;
|
|
296
|
+
}
|
|
297
|
+
|
|
297
298
|
export {
|
|
298
|
-
MAX_TUI_THINKING_WORD_LENGTH,
|
|
299
|
-
normalizeTuiThinkingWord,
|
|
300
299
|
resolvePersistPath,
|
|
301
300
|
resolveActualTarget,
|
|
302
301
|
filterSafeForProject,
|
|
@@ -308,6 +307,8 @@ export {
|
|
|
308
307
|
setAutoSuggestions,
|
|
309
308
|
getSuggestions,
|
|
310
309
|
removeSuggestions,
|
|
311
|
-
clearSuggestions
|
|
310
|
+
clearSuggestions,
|
|
311
|
+
MAX_TUI_THINKING_WORD_LENGTH,
|
|
312
|
+
normalizeTuiThinkingWord
|
|
312
313
|
};
|
|
313
|
-
//# sourceMappingURL=chunk-
|
|
314
|
+
//# sourceMappingURL=chunk-QZKWRNIP.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/settings-menu.ts", "../src/services/suggestion-store.ts", "../src/tui-thinking-word.ts"],
|
|
4
|
+
"sourcesContent": ["import * as fs from 'node:fs/promises';\nimport * as path from 'node:path';\nimport { atomicWrite, deepMerge } from '@wrongstack/core/utils';\nimport { ConfigError, ERROR_CODES, FsError, type SecretVault } from '@wrongstack/core/types';\nimport type { ConfigStore } from '@wrongstack/core/types';\nimport { decryptConfigSecrets, encryptConfigSecrets } from '@wrongstack/core/security';\n\n/** Configuration and storage dependencies needed to persist a setting.\n * This is safe to call from non-interactive surfaces such as the TUI,\n * headless runs, and the arg-driven `/settings` slash command. */\nexport interface PersistSettingDeps {\n configStore: ConfigStore;\n /** Path to the active profile config (~/.wrongstack/profiles/<name>/config.json). */\n profileConfigPath: string;\n /** Per-project config path (<project>/.wrongstack/config.json).\n * Used when configScope === 'project'. Lives inside the project\n * root so it can be gitignored or team-shared. */\n inProjectConfigPath?: string | undefined;\n vault: SecretVault;\n /** Force writes to the active profile config even when configScope is project. */\n forceGlobal?: boolean | undefined;\n /**\n * Optional resolver that recomputes the profile config path for a given\n * profile name. When set, {@link resolveActualTarget} uses this to\n * re-resolve the target path when `decrypted.activeProfile` differs from\n * the one used to compute `profileConfigPath`. This prevents the write\n * from landing in the wrong profile when the mutator changes activeProfile.\n */\n resolveProfilePath?: ((profileName: string) => string) | undefined;\n}\n\nexport function resolvePersistPath(deps: PersistSettingDeps): string {\n if (deps.forceGlobal) return deps.profileConfigPath;\n const scope = (deps.configStore.get() as { configScope?: string | undefined }).configScope;\n if (scope === 'project' && deps.inProjectConfigPath) {\n return deps.inProjectConfigPath;\n }\n // Default user scope always means the active profile, never the root bootstrap.\n return deps.profileConfigPath;\n}\n\n/**\n * Re-resolve the target path after a mutator may have changed configScope.\n * Handles the two settings targets: project config or active profile config.\n */\nexport function resolveActualTarget(\n deps: PersistSettingDeps,\n decrypted: Record<string, unknown>,\n fallbackPath: string,\n): string {\n if (deps.forceGlobal) return deps.profileConfigPath;\n const newScope = decrypted.configScope as string | undefined;\n // Recompute profile path when activeProfile changed in the mutation block.\n const newProfileName = decrypted.activeProfile as string | undefined;\n const effectiveProfilePath =\n newProfileName && deps.resolveProfilePath\n ? deps.resolveProfilePath(newProfileName)\n : deps.profileConfigPath;\n if (newScope === 'project' && deps.inProjectConfigPath) {\n return deps.inProjectConfigPath;\n }\n if (newScope === 'global') {\n return effectiveProfilePath;\n }\n return fallbackPath;\n}\n\n/**\n * Returns true when the target path is the trusted active-profile config, so\n * secrets are NOT stripped before writing.\n * Returns false for project-scoped paths where credentials must be filtered.\n */\nfunction isProfileOrGlobalTarget(actualTarget: string, deps: PersistSettingDeps): boolean {\n return actualTarget === deps.profileConfigPath;\n}\n\nasync function ensureProjectDir(filePath: string): Promise<void> {\n const dir = path.dirname(filePath);\n try {\n await fs.mkdir(dir, { recursive: true });\n } catch {\n // Directory already exists or is inaccessible \u2014 the write will surface the real error.\n }\n}\n\n/**\n * When configScope changed mid-save, the write target differs from the file\n * that was read. Read the *destination* file, decrypt it, and deep-merge the\n * mutated `source` on top (prefer-patch so the user's explicit changes win).\n * This prevents a sparse source (e.g. project config without credentials)\n * from clobbering credential-bearing fields in the destination (e.g. profile\n * config with apiKey / providers / mcpServers).\n *\n * Returns `source` unchanged when destination does not exist yet.\n */\nasync function mergeWithDestinationIfExists(\n destPath: string,\n source: Record<string, unknown>,\n vault: SecretVault,\n): Promise<Record<string, unknown>> {\n let destRaw: string;\n try {\n destRaw = await fs.readFile(destPath, 'utf8');\n } catch (err) {\n // Only swallow ENOENT (destination doesn't exist yet).\n // All other errors (EACCES, EIO, etc.) propagate as-is.\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') {\n return source;\n }\n throw err;\n }\n let destParsed: Record<string, unknown>;\n try {\n destParsed = JSON.parse(destRaw) as Record<string, unknown>;\n } catch (err) {\n // Corrupt destination \u2014 don't merge, just write our content.\n process.stderr.write(JSON.stringify({\n level: 'warn',\n event: 'settings_menu_merge_skipped',\n message: `Skipping merge into corrupt destination file: ${destPath}`,\n timestamp: new Date().toISOString(),\n }) + '\\n');\n void err;\n return source;\n }\n const destDecrypted = decryptConfigSecrets(destParsed, vault) as Record<string, unknown>;\n return deepMerge(destDecrypted, source) as Record<string, unknown>;\n}\n\n/**\n * Fields that are safe to persist in a per-project `.wrongstack/config.json`.\n * Credential-bearing fields (apiKey, providers, sync) MUST NOT appear here \u2014\n * they stay in the active profile config only. The active profile always gets the\n * full unfiltered object.\n *\n * When adding a field here, ask: \"Would I commit this to a shared repo?\"\n * If the answer is no, it doesn't belong on this list.\n */\nconst PROJECT_SAFE_FIELDS = new Set([\n 'provider',\n 'model',\n 'fallbackModels',\n // Kept in sync with core's IN_PROJECT_ALLOWED_KEYS (config-loader.ts): these\n // model-routing prefs are explicitly safe to READ from a per-project config,\n // so the writer must not strip them \u2014 otherwise a scope=project /fallback\n // profile/fav write (or a TUI full-config write under project scope) would be\n // silently dropped while the loader would happily accept them on next boot.\n 'fallbackProfiles',\n 'favoriteModels',\n 'favoriteModelsOnly',\n 'modelAvailabilitySchedule',\n 'fallbackAuto',\n 'models',\n 'modelMatrix',\n 'maxConcurrent',\n 'autonomy',\n 'hints',\n 'nextPrediction',\n 'debugStream',\n 'configScope',\n 'yolo',\n 'features',\n 'context',\n 'log',\n 'session',\n 'indexing',\n 'tools',\n 'launch',\n 'circuitBreaker',\n 'adaptiveConcurrency',\n 'modelRuntime',\n 'Sage',\n]);\n\n/**\n * Strip credential-bearing and machine-specific fields from a config object\n * so it is safe to write into a per-project `.wrongstack/config.json` file.\n * Returns a new object \u2014 the original is not mutated.\n */\nexport function filterSafeForProject(cfg: Record<string, unknown>): Record<string, unknown> {\n const out: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(cfg)) {\n if (PROJECT_SAFE_FIELDS.has(key)) {\n out[key] = value;\n }\n }\n return out;\n}\n\n/**\n * Read the config file, apply `mutator` to its `autonomy` block, and write it\n * back atomically (then mirror into the in-memory config store). Pure I/O \u2014 no\n * terminal interaction \u2014 so it works identically under the plain REPL and the\n * Ink TUI.\n */\nexport async function persistAutonomySetting(\n deps: PersistSettingDeps,\n mutator: (autonomy: {\n autoProceedDelayMs?: number | undefined;\n defaultMode?: string | undefined;\n enhance?: boolean | undefined;\n enhanceDelayMs?: number | undefined;\n enhanceLanguage?: string | undefined;\n }) => void,\n): Promise<void> {\n const targetPath = resolvePersistPath(deps);\n await ensureProjectDir(targetPath);\n\n let raw: string;\n let fileExists = true;\n try {\n raw = await fs.readFile(targetPath, 'utf8');\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== 'ENOENT') {\n throw new FsError({\n message: `Could not read ${targetPath}`,\n code: ERROR_CODES.FS_READ_FAILED,\n path: targetPath,\n cause: err,\n });\n }\n fileExists = false;\n raw = '{}';\n }\n\n let parsed: Record<string, unknown>;\n try {\n parsed = JSON.parse(raw) as Record<string, unknown>;\n } catch (err) {\n if (fileExists) {\n throw new ConfigError({\n message: `Config at ${targetPath} is not valid JSON`,\n code: ERROR_CODES.CONFIG_PARSE_FAILED,\n context: { path: targetPath },\n cause: err,\n });\n }\n parsed = {};\n }\n\n const decrypted = decryptConfigSecrets(parsed, deps.vault) as Record<string, unknown>;\n const autonomy = (decrypted.autonomy as Record<string, unknown>) ?? {};\n mutator(\n autonomy as { autoProceedDelayMs?: number | undefined; defaultMode?: string | undefined },\n );\n decrypted.autonomy = autonomy;\n\n // Re-resolve path \u2014 the mutator might have changed configScope.\n const actualTarget = resolveActualTarget(deps, decrypted as Record<string, unknown>, targetPath);\n if (actualTarget !== targetPath) {\n await ensureProjectDir(actualTarget);\n }\n\n // When configScope changed mid-save, merge into the destination file's\n // existing content so credential-bearing fields are preserved.\n const effectiveConfig =\n actualTarget !== targetPath\n ? await mergeWithDestinationIfExists(actualTarget, decrypted, deps.vault)\n : decrypted;\n\n // When writing to the project-local config, strip credentials so\n // apiKey / providers / sync never leak into a per-project file.\n const toWrite =\n isProfileOrGlobalTarget(actualTarget, deps) ? effectiveConfig : filterSafeForProject(effectiveConfig);\n\n const encrypted = encryptConfigSecrets(toWrite, deps.vault);\n await atomicWrite(actualTarget, JSON.stringify(encrypted, null, 2), { mode: 0o600 });\n\n // Also update the in-memory config store so changes are immediately visible\n deps.configStore.update({\n autonomy: decrypted.autonomy as Parameters<typeof deps.configStore.update>[0]['autonomy'],\n });\n}\n\n/**\n * Read the config file, apply `mutator` to the full decrypted object, and\n * write it back atomically. This is used for small top-level settings that do\n * not warrant a dedicated helper.\n */\nexport async function persistConfigSetting(\n deps: PersistSettingDeps,\n mutator: (config: Record<string, unknown>) => void,\n): Promise<void> {\n const targetPath = resolvePersistPath(deps);\n await ensureProjectDir(targetPath);\n\n let raw: string;\n let fileExists = true;\n try {\n raw = await fs.readFile(targetPath, 'utf8');\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== 'ENOENT') {\n throw new FsError({\n message: `Could not read ${targetPath}`,\n code: ERROR_CODES.FS_READ_FAILED,\n path: targetPath,\n cause: err,\n });\n }\n fileExists = false;\n raw = '{}';\n }\n\n let parsed: Record<string, unknown>;\n try {\n parsed = JSON.parse(raw) as Record<string, unknown>;\n } catch (err) {\n if (fileExists) {\n throw new ConfigError({\n message: `Config at ${targetPath} is not valid JSON`,\n code: ERROR_CODES.CONFIG_PARSE_FAILED,\n context: { path: targetPath },\n cause: err,\n });\n }\n parsed = {};\n }\n\n const decrypted = decryptConfigSecrets(parsed, deps.vault) as Record<string, unknown>;\n mutator(decrypted);\n\n // If the mutator changed configScope, re-resolve the target path.\n // Without this, a scope change from 'project' \u2192 'global' would write\n // to the old project path instead of the new global one.\n const actualTarget = resolveActualTarget(deps, decrypted, targetPath);\n\n // Ensure the directory exists if we're writing to a new path\n if (actualTarget !== targetPath) {\n await ensureProjectDir(actualTarget);\n }\n\n // When configScope changed mid-save, merge into the destination file's\n // existing content so credential-bearing fields are preserved.\n const effectiveConfig =\n actualTarget !== targetPath\n ? await mergeWithDestinationIfExists(actualTarget, decrypted, deps.vault)\n : decrypted;\n\n // When writing to the project-local config, strip credentials so\n // apiKey / providers / sync never leak into a per-project file.\n const toWrite =\n isProfileOrGlobalTarget(actualTarget, deps) ? effectiveConfig : filterSafeForProject(effectiveConfig);\n\n const encrypted = encryptConfigSecrets(toWrite, deps.vault);\n await atomicWrite(actualTarget, JSON.stringify(encrypted, null, 2), { mode: 0o600 });\n deps.configStore.update(decrypted as Parameters<typeof deps.configStore.update>[0]);\n}\n\n/**\n * Persist Telegram plugin config to `extensions.telegram` in the active profile config\n * file. Mirrors `persistAutonomySetting` \u2014 reads the config, applies the\n * mutator, encrypts secrets, writes atomically, then updates ConfigStore.\n */\nexport async function persistTelegramConfig(\n deps: PersistSettingDeps,\n mutator: (telegram: Record<string, unknown>) => void,\n): Promise<void> {\n const targetPath = deps.profileConfigPath;\n let raw: string;\n let fileExists = true;\n try {\n raw = await fs.readFile(targetPath, 'utf8');\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== 'ENOENT') {\n throw new FsError({\n message: `Could not read ${targetPath}: ${(err as Error).message}`,\n code: 'FS_READ_FAILED',\n path: targetPath,\n context: { operation: 'readGlobalConfig', phase: 'read' },\n cause: err,\n });\n }\n fileExists = false;\n raw = '{}';\n }\n\n let parsed: Record<string, unknown>;\n try {\n parsed = JSON.parse(raw) as Record<string, unknown>;\n } catch (err) {\n if (fileExists) {\n throw new ConfigError({\n message: `Config at ${targetPath} is not valid JSON: ${(err as Error).message}`,\n code: 'CONFIG_PARSE_FAILED',\n context: { filePath: targetPath, operation: 'readProfileConfig' },\n cause: err,\n });\n }\n parsed = {};\n }\n\n const decrypted = decryptConfigSecrets(parsed, deps.vault) as Record<string, unknown>;\n const extensions = (decrypted.extensions as Record<string, Record<string, unknown>>) ?? {};\n const telegram = extensions.telegram ?? {};\n mutator(telegram);\n extensions.telegram = telegram;\n decrypted.extensions = extensions;\n\n const encrypted = encryptConfigSecrets(decrypted, deps.vault);\n await atomicWrite(targetPath, JSON.stringify(encrypted, null, 2), { mode: 0o600 });\n\n // Also update the in-memory config store so changes are immediately visible\n deps.configStore.update({\n extensions: extensions as NonNullable<\n Parameters<typeof deps.configStore.update>[0]['extensions']\n >,\n });\n}\n\n/**\n * Derive the filesystem-access pair (`features.allowOutsideProjectRoot` and\n * `tools.restrictToProjectRoot`) from a save input. The two are inverses\n * of each other and the codebase intentionally keeps both in sync for\n * backward compatibility with older readers that only know about\n * `tools.restrictToProjectRoot`.\n *\n * Single source of truth: `allowOutsideProjectRoot`. If the caller only\n * sets `restrictFsToRoot`, it is converted to its inverse. If both are\n * set (which the picker should not do, but defensive code paths may),\n * `allowOutsideProjectRoot` wins. Returns `undefined` if neither is set,\n * so the caller can skip the write.\n *\n * The input is duck-typed (only the two relevant fields are read) so any\n * caller can pass a partial shape: the TUI's full `LiveSettingsInput`,\n * a slash-command's `{ restrictFsToRoot: boolean }`, or any future\n * save-handler that only carries these two knobs.\n */\nexport function deriveFsAccessPair(input: {\n allowOutsideProjectRoot?: boolean | undefined;\n restrictFsToRoot?: boolean | undefined;\n}): { allowOutsideProjectRoot: boolean; restrictToProjectRoot: boolean } | undefined {\n if (input.allowOutsideProjectRoot !== undefined) {\n const allow = input.allowOutsideProjectRoot;\n return { allowOutsideProjectRoot: allow, restrictToProjectRoot: !allow };\n }\n if (input.restrictFsToRoot !== undefined) {\n const restrict = input.restrictFsToRoot;\n return { allowOutsideProjectRoot: !restrict, restrictToProjectRoot: restrict };\n }\n return undefined;\n}\n", "/**\n * Shared suggestion state used by command and runtime surfaces.\n *\n * Previously, suggestions were stored in a closure variable (`currentSuggestions`)\n * inside cli-main.ts, accessed via the `onSuggestions` callback on\n * `SlashCommandContext`. This closure-based approach works correctly in the REPL\n * but can fail in the TUI when the callback goes through multiple indirections\n * (SlashCommandRegistry \u2192 SlashCommand.run \u2192 closed-over opts).\n *\n * By using a module-level store, both /suggest (writer) and /next (reader)\n * access the same array directly, bypassing the callback indirection entirely.\n * The CLI's `onSuggestions` callback now reads/writes this shared store to\n * maintain backward compatibility with the REPL autonomy loop's\n * `getSuggestions`/`onSuggestionsParsed` flow.\n *\n * Auto suggestions (items with auto=\"true\" attribute) are stored separately\n * for YOLO+auto autonomy mode.\n */\n\nlet sharedSuggestions: string[] = [];\nlet sharedAutoSuggestions: string[] = [];\n\n/** Store suggestions (called by /suggest and onSuggestionsParsed). */\nexport function setSuggestions(suggestions: string[]): void {\n sharedSuggestions = suggestions;\n}\n\n/** Store auto suggestions (items with auto=\"true\" attribute). Called by onAutoSuggestionsParsed. */\nexport function setAutoSuggestions(suggestions: string[]): void {\n sharedAutoSuggestions = suggestions;\n}\n\n/** Retrieve current suggestions (called by /next selection and getSuggestions). */\nexport function getSuggestions(): string[] {\n return sharedSuggestions;\n}\n\n/** Retrieve current auto suggestions (called by YOLO+auto autonomy). */\nexport function getAutoSuggestions(): string[] {\n return sharedAutoSuggestions;\n}\n\n/**\n * Remove suggestions at the given 0-based indices.\n * Called by /next selection so selected prompts cannot be re-selected.\n * Also clears auto-suggestions since an explicit user selection should\n * override any auto-submit queue.\n */\nexport function removeSuggestions(indices: number[]): void {\n if (indices.length === 0) return;\n const set = new Set(indices);\n sharedSuggestions = sharedSuggestions.filter((_, i) => !set.has(i));\n sharedAutoSuggestions = [];\n}\n\n/** Clear suggestions (called by /next clear). */\nexport function clearSuggestions(): void {\n sharedSuggestions = [];\n sharedAutoSuggestions = [];\n}\n", "export const DEFAULT_TUI_THINKING_WORD = 'thinking';\nexport const MAX_TUI_THINKING_WORD_LENGTH = 16;\n\n/** Normalize autonomy.thinkingWord to a single short word for the TUI statusline. */\nexport function normalizeTuiThinkingWord(value: unknown): string {\n if (typeof value !== 'string') return DEFAULT_TUI_THINKING_WORD;\n const word = value.trim();\n if (word.length === 0 || word.length > MAX_TUI_THINKING_WORD_LENGTH) {\n return DEFAULT_TUI_THINKING_WORD;\n }\n if (!/^[\\p{L}\\p{N}_-]+$/u.test(word)) return DEFAULT_TUI_THINKING_WORD;\n return word;\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -257,9 +257,22 @@ async function trySaveTasksFromAIOutput(aiOutput) {
|
|
|
257
257
|
for (const task of validTasks) addTaskToTracker(existingTracker, task);
|
|
258
258
|
return true;
|
|
259
259
|
}
|
|
260
|
-
const store = new DefaultTaskStore();
|
|
260
|
+
const store = sddState.getTaskStore() ?? new DefaultTaskStore();
|
|
261
261
|
const tracker = new TaskTracker({ store });
|
|
262
|
-
|
|
262
|
+
let graph;
|
|
263
|
+
try {
|
|
264
|
+
graph = await tracker.createGraph(session.spec.id, session.spec.title);
|
|
265
|
+
} catch (error) {
|
|
266
|
+
console.warn(
|
|
267
|
+
JSON.stringify({
|
|
268
|
+
level: "warn",
|
|
269
|
+
event: "sdd.task_graph_create_failed",
|
|
270
|
+
message: error instanceof Error ? error.message : String(error),
|
|
271
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
272
|
+
})
|
|
273
|
+
);
|
|
274
|
+
return false;
|
|
275
|
+
}
|
|
263
276
|
for (const task of validTasks) addTaskToTracker(tracker, task);
|
|
264
277
|
sddState.setTaskStore(store);
|
|
265
278
|
sddState.setTaskTracker(tracker);
|
|
@@ -422,4 +435,4 @@ export {
|
|
|
422
435
|
getTaskGraphId,
|
|
423
436
|
getTaskTrackerExport
|
|
424
437
|
};
|
|
425
|
-
//# sourceMappingURL=chunk-
|
|
438
|
+
//# sourceMappingURL=chunk-XTIXVJXP.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/services/sdd/state.ts", "../src/services/sdd/project-context.ts", "../src/services/sdd/spec-detection.ts", "../src/services/sdd/task-manager.ts"],
|
|
4
|
+
"sourcesContent": ["import type { Context } from '@wrongstack/core/agent';\nimport {\n type AISpecBuilder,\n type AISpecPhase,\n SpecVersioning,\n type TaskStore,\n type TaskTracker,\n} from '@wrongstack/sdd';\n\n/** Key used to store SDD session state in ctx.meta for session isolation. */\nexport const SDD_META_KEY = 'sdd.state';\n\n/** Single shared SDD session state for the process lifetime. */\nexport class SDDState {\n private builder: AISpecBuilder | null = null;\n private taskStore: TaskStore | null = null;\n private taskTracker: TaskTracker | null = null;\n private taskGraphId: string | null = null;\n private sessionStartTime: number = Date.now();\n private phaseStartTime: number = Date.now();\n private versioning: SpecVersioning | null = null;\n\n getBuilder(): AISpecBuilder | null {\n return this.builder;\n }\n setBuilder(b: AISpecBuilder | null) {\n this.builder = b;\n }\n getTaskStore(): TaskStore | null {\n return this.taskStore;\n }\n setTaskStore(s: TaskStore | null) {\n this.taskStore = s;\n }\n getTaskTracker(): TaskTracker | null {\n return this.taskTracker;\n }\n setTaskTracker(t: TaskTracker | null) {\n this.taskTracker = t;\n }\n getTaskGraphId(): string | null {\n return this.taskGraphId;\n }\n setTaskGraphId(id: string | null) {\n this.taskGraphId = id;\n }\n getSessionStartTime(): number {\n return this.sessionStartTime;\n }\n setSessionStartTime(t: number) {\n this.sessionStartTime = t;\n }\n setPhaseStartTime(t: number) {\n this.phaseStartTime = t;\n }\n getPhaseStartTime(): number {\n return this.phaseStartTime;\n }\n getSessionElapsed(): number {\n return Date.now() - this.sessionStartTime;\n }\n getPhaseElapsed(): number {\n return Date.now() - this.phaseStartTime;\n }\n getVersioning(): SpecVersioning {\n if (this.versioning === null) this.versioning = new SpecVersioning();\n return this.versioning;\n }\n\n clearTaskState(): void {\n this.taskStore = null;\n this.taskTracker = null;\n this.taskGraphId = null;\n }\n\n getContext(): string | null {\n if (!this.builder) return null;\n const session = this.builder.getSession();\n if (session.phase === 'done') return null;\n return this.builder.getAIPrompt();\n }\n\n getPhase(): AISpecPhase | null {\n return this.builder?.getPhase() ?? null;\n }\n}\n\n/** Process-lifetime singleton \u2014 used when no Context is available (CLI single-session mode). */\nexport const sddState = new SDDState();\n\n/**\n * Get or create the SDD state for the current session.\n * Uses ctx.meta so each concurrent browser/REPL session has isolated state.\n */\nexport function getSessionState(ctx: Context | undefined): SDDState {\n if (!ctx) return sddState;\n let state = ctx.meta[SDD_META_KEY] as SDDState | undefined;\n if (!state) {\n state = new SDDState();\n ctx.meta[SDD_META_KEY] = state;\n }\n return state;\n}\n", "import type { AISpecBuilder, AISpecPhase, SpecIndexEntry, SpecStore } from '@wrongstack/sdd';\n// Shared with the WebUI wizard \u2014 single implementation in @wrongstack/sdd.\nexport { gatherProjectContext } from '@wrongstack/sdd';\nimport { sddState } from './state.js';\n\nexport function getActiveBuilder(): AISpecBuilder | null {\n return sddState.getBuilder();\n}\n\nexport function getActiveSDDContext(): string | null {\n return sddState.getContext();\n}\n\nexport function getActiveSDDPhase(): AISpecPhase | null {\n return sddState.getPhase();\n}\n\nexport async function findSpec(store: SpecStore, idOrTitle: string) {\n if (!idOrTitle) return null;\n const byId = await store.load(idOrTitle);\n if (byId) return byId;\n const all = await store.list();\n const match = all.find(\n (e: SpecIndexEntry) =>\n e.id.startsWith(idOrTitle) || e.title.toLowerCase().includes(idOrTitle.toLowerCase()),\n );\n if (match) return store.load(match.id);\n return null;\n}\n", "import { sddState } from './state.js';\n\n/**\n * Returns true if the text looks like conversational/explanatory output\n * rather than a structured implementation plan.\n */\nexport function isExplanatoryText(text: string): boolean {\n const lower = text.toLowerCase();\n return (\n lower.startsWith(\"i'\") ||\n lower.startsWith('i will') ||\n lower.startsWith('let me') ||\n lower.startsWith(\"here's my\") ||\n lower.startsWith('here is my') ||\n lower.startsWith(\"i'm going to\") ||\n lower.startsWith('first, let me') ||\n lower.startsWith('sure') ||\n lower.startsWith('of course') ||\n lower.startsWith('okay') ||\n lower.startsWith('ok,') ||\n lower.startsWith('sounds good') ||\n lower.startsWith('no problem') ||\n (text.split('\\n').length < 3 && !text.includes('.'))\n );\n}\n\n/**\n * Parse a spec from AI output text and save it to the active session.\n * Returns true if a spec was found and saved.\n */\nexport async function trySaveSpecFromAIOutput(aiOutput: string): Promise<boolean> {\n const builder = sddState.getBuilder();\n if (!builder) return false;\n const spec = builder.tryParseSpecFromOutput(aiOutput);\n if (!spec) return false;\n builder.setSpec(spec);\n return true;\n}\n\n/**\n * Try to save implementation plan from AI output during implementation phase.\n * Returns true if a plan was saved.\n */\nexport function trySaveImplementationPlan(aiOutput: string): boolean {\n const builder = sddState.getBuilder();\n if (!builder) return false;\n const session = builder.getSession();\n if (session.phase !== 'implementation') return false;\n\n const current = session.implementation ?? '';\n\n const jsonMatch = aiOutput.match(/```json\\s*\\[/);\n if (jsonMatch?.index && jsonMatch.index > 0) {\n const plan = aiOutput.substring(0, jsonMatch.index).trim();\n if (plan.length > 50 && plan !== current && !isExplanatoryText(plan)) {\n builder.setImplementation(plan);\n return true;\n }\n }\n\n if (\n aiOutput.length > 100 &&\n !aiOutput.includes('```json') &&\n aiOutput !== current &&\n !isExplanatoryText(aiOutput)\n ) {\n builder.setImplementation(aiOutput.trim());\n return true;\n }\n\n return false;\n}\n\n/**\n * Auto-detect task completion patterns in AI output and mark tasks.\n * Returns the number of tasks marked as completed.\n */\nexport function autoDetectTaskCompletion(aiOutput: string): number {\n const tracker = sddState.getTaskTracker();\n if (!tracker) return 0;\n const pending = tracker.getAllNodes({ status: ['pending', 'in_progress'] });\n if (pending.length === 0) return 0;\n\n let completed = 0;\n const lines = aiOutput.split('\\n');\n\n for (const line of lines) {\n const trimmed = line.trim();\n\n const sddDoneMatch = trimmed.match(/\\/sdd\\s+done\\s+(.+)/i);\n if (sddDoneMatch?.[1]) {\n const target = sddDoneMatch[1].trim();\n const num = Number(target);\n if (!Number.isNaN(num) && num >= 1 && num <= pending.length) {\n const node = pending[num - 1];\n if (node && node.status !== 'completed') {\n tracker.updateNodeStatus(node.id, 'completed');\n completed++;\n }\n } else {\n const match = pending.find(\n (n) =>\n n.title.toLowerCase().includes(target.toLowerCase()) ||\n target.toLowerCase().includes(n.title.toLowerCase()),\n );\n if (match && match.status !== 'completed') {\n tracker.updateNodeStatus(match.id, 'completed');\n completed++;\n }\n }\n continue;\n }\n\n const checkmarkMatch = trimmed.match(/^\u2705\\s*(?:Task:\\s*)?(.+)/i);\n if (checkmarkMatch?.[1]) {\n const title = checkmarkMatch[1].trim();\n const match = pending.find(\n (n) =>\n n.title.toLowerCase().includes(title.toLowerCase()) ||\n title.toLowerCase().includes(n.title.toLowerCase()),\n );\n if (match && match.status !== 'completed') {\n tracker.updateNodeStatus(match.id, 'completed');\n completed++;\n }\n continue;\n }\n\n const taskNumMatch = trimmed.match(/Task\\s+(\\d+)\\s*[:]\\s*(?:complete|done|finished)/i);\n if (taskNumMatch?.[1]) {\n const num = Number(taskNumMatch[1]);\n if (num >= 1 && num <= pending.length) {\n const node = pending[num - 1];\n if (node && node.status !== 'completed') {\n tracker.updateNodeStatus(node.id, 'completed');\n completed++;\n }\n }\n continue;\n }\n\n const completedMatch = trimmed.match(/^(?:Completed|Done|Finished)\\s*[:]\\s*(.+)/i);\n if (completedMatch?.[1]) {\n const title = completedMatch[1].trim();\n const match = pending.find(\n (n) =>\n n.title.toLowerCase().includes(title.toLowerCase()) ||\n title.toLowerCase().includes(n.title.toLowerCase()),\n );\n if (match && match.status !== 'completed') {\n tracker.updateNodeStatus(match.id, 'completed');\n completed++;\n }\n }\n }\n\n return completed;\n}\n", "import type { TaskProgress } from '@wrongstack/core/types';\nimport { expectDefined } from '@wrongstack/core/utils';\nimport { DefaultTaskStore, renderProgress, TaskTracker } from '@wrongstack/sdd';\nimport { sddState } from './state.js';\n\nexport type { TaskProgress };\nexport { renderProgress };\n\n/**\n * Format elapsed milliseconds as a human-readable string.\n */\nexport function formatElapsed(ms: number): string {\n if (ms < 1000) return `${ms}ms`;\n const s = Math.floor(ms / 1000);\n if (s < 60) return `${s}s`;\n const m = Math.floor(s / 60);\n const remS = s % 60;\n if (m < 60) return remS > 0 ? `${m}m ${remS}s` : `${m}m`;\n const h = Math.floor(m / 60);\n const remM = m % 60;\n return `${h}h ${remM}m`;\n}\n\nfunction addTaskToTracker(tracker: TaskTracker, task: Record<string, unknown>): void {\n tracker.addNode({\n title: String(task.title),\n description: String(task.description ?? ''),\n type: (['feature', 'bugfix', 'refactor', 'docs', 'test', 'chore'].includes(String(task.type))\n ? String(task.type)\n : 'feature') as 'feature' | 'bugfix' | 'refactor' | 'docs' | 'test' | 'chore',\n priority: (['critical', 'high', 'medium', 'low'].includes(String(task.priority))\n ? String(task.priority)\n : 'medium') as 'critical' | 'high' | 'medium' | 'low',\n status: 'pending',\n estimateHours: Number(task.estimateHours) || 2,\n tags: Array.isArray(task.tags) ? task.tags.map(String) : [],\n });\n}\n\nexport async function trySaveTasksFromAIOutput(aiOutput: string): Promise<boolean> {\n const builder = sddState.getBuilder();\n if (!builder) return false;\n const session = builder.getSession();\n if (!session.spec) return false;\n\n const json = builder.extractJSONArray(aiOutput);\n if (!json) return false;\n\n let tasks: Array<Record<string, unknown>>;\n try {\n tasks = JSON.parse(json);\n } catch {\n return false;\n }\n if (!Array.isArray(tasks) || tasks.length === 0) return false;\n\n const validTasks = tasks.filter(\n (t) => t && typeof t === 'object' && typeof t.title === 'string' && t.title.length > 0,\n );\n if (validTasks.length === 0) return false;\n\n const existingTracker = sddState.getTaskTracker();\n if (existingTracker) {\n for (const task of validTasks) addTaskToTracker(existingTracker, task);\n return true;\n }\n\n const store = sddState.getTaskStore() ?? new DefaultTaskStore();\n const tracker = new TaskTracker({ store });\n // `createGraph` now awaits `store.saveGraph` (real fs I/O for\n // TaskGraphStore). ENOSPC / EACCES on the on-disk store used to be\n // unreachable because the legacy DefaultTaskStore was a no-op; the\n // contract documented \"best-effort persistence\" so a failed write must\n // not reject through the caller. Fall back to tracking in-memory only\n // when the store rejects \u2014 the in-memory tracker still drives the run.\n let graph: Awaited<ReturnType<typeof tracker.createGraph>>;\n try {\n graph = await tracker.createGraph(session.spec.id, session.spec.title);\n } catch (error) {\n console.warn(\n JSON.stringify({\n level: 'warn',\n event: 'sdd.task_graph_create_failed',\n message: error instanceof Error ? error.message : String(error),\n timestamp: new Date().toISOString(),\n }),\n );\n return false;\n }\n for (const task of validTasks) addTaskToTracker(tracker, task);\n sddState.setTaskStore(store);\n sddState.setTaskTracker(tracker);\n sddState.setTaskGraphId(graph.id);\n // Fire-and-forget save is unsafe here \u2014 `setTaskGraphId` is awaited precisely\n // because the next mutation or caller's own `await saveSession()` must not\n // race a stale fire-and-forget rename that silently reverts the persisted\n // taskGraphId. The awaited save ensures the persisted snapshot matches\n // sddState.setTaskGraphId before this function returns.\n await builder.setTaskGraphId(graph.id);\n return true;\n}\n\nexport function getTaskProgress(): TaskProgress | null {\n const tracker = sddState.getTaskTracker();\n if (!tracker) return null;\n return tracker.getProgress();\n}\n\nexport function getCurrentTask(): {\n id: string;\n title: string;\n description: string;\n priority: string;\n estimateHours: number;\n tags: string[];\n startedAt: number | undefined;\n} | null {\n const tracker = sddState.getTaskTracker();\n if (!tracker) return null;\n const nodes = tracker.getAllNodes({ status: ['in_progress'] });\n if (nodes.length === 0) return null;\n const n = expectDefined(nodes[0]);\n return {\n id: n.id,\n title: n.title,\n description: n.description,\n priority: n.priority,\n estimateHours: n.estimateHours ?? 0,\n tags: n.tags ?? [],\n startedAt: n.startedAt,\n };\n}\n\nexport function advanceToNextTask(): boolean {\n const tracker = sddState.getTaskTracker();\n if (!tracker) return false;\n const pending = tracker.getAllNodes({ status: ['pending'] });\n for (const n of pending) {\n if (tracker.canStart(n.id)) {\n tracker.updateNodeStatus(n.id, 'in_progress');\n return true;\n }\n }\n return false;\n}\n\nexport function getTaskListText(): string | null {\n const tracker = sddState.getTaskTracker();\n if (!tracker) return null;\n const nodes = tracker.getAllNodes();\n if (nodes.length === 0) return null;\n return nodes\n .map((n, i) => {\n const status = n.status === 'completed' ? '\u2705' : n.status === 'in_progress' ? '\uD83D\uDD04' : '\u23F3';\n return `${i + 1}. ${status} [${n.priority}] ${n.title}`;\n })\n .join('\\n');\n}\n\nexport function renderTaskListWithProgress(): string | null {\n const tracker = sddState.getTaskTracker();\n if (!tracker) return null;\n const nodes = tracker.getAllNodes();\n if (nodes.length === 0) return null;\n\n const progress = tracker.getProgress();\n const phase = sddState.getPhase();\n const phaseLabel: Record<string, string> = {\n questioning: '\u2753 Questioning',\n spec_review: '\uD83D\uDCCB Spec Review',\n implementation: '\uD83C\uDFD7\uFE0F Implementation',\n task_review: '\uD83D\uDCDD Task Review',\n executing: '\u26A1 Executing',\n done: '\u2705 Done',\n };\n const lines = [\n `**${phaseLabel[phase ?? ''] ?? phase} \u2014 Task Status**`,\n '',\n renderProgress(progress),\n '',\n ];\n\n const sorted = [...nodes].sort((a, b) => {\n const order: Record<string, number> = {\n in_progress: 0,\n pending: 1,\n review: 2,\n blocked: 3,\n failed: 4,\n completed: 5,\n };\n return (order[a.status] ?? 6) - (order[b.status] ?? 6);\n });\n for (let i = 0; i < sorted.length; i++) {\n const n = expectDefined(sorted[i]);\n const status =\n n.status === 'completed'\n ? '\u2705'\n : n.status === 'in_progress'\n ? '\uD83D\uDD04'\n : n.status === 'failed'\n ? '\u274C'\n : n.status === 'blocked'\n ? '\uD83D\uDEAB'\n : n.status === 'review'\n ? '\uD83D\uDC41'\n : '\u23F3';\n const title = n.title.length > 50 ? n.title.slice(0, 49) + '\u2026' : n.title;\n let elapsed = '';\n if (n.status === 'in_progress' && n.startedAt)\n elapsed = ` \u00B7 ${formatElapsed(Date.now() - n.startedAt)}`;\n lines.push(`${i + 1}. ${status} ${title}${elapsed}`);\n }\n return lines.join('\\n');\n}\n\nexport function getCurrentExecutingContext(): string | null {\n const tracker = sddState.getTaskTracker();\n if (!tracker) return null;\n const nodes = tracker.getAllNodes({ status: ['in_progress'] });\n if (nodes.length === 0) return null;\n const n = expectDefined(nodes[0]);\n const elapsed = n.startedAt ? ` \u00B7 elapsed: ${formatElapsed(Date.now() - n.startedAt)}` : '';\n const progress = tracker.getProgress();\n return [\n `**NOW EXECUTING:** \"${n.title}\"${elapsed}`,\n `Description: ${n.description.split('\\n')[0] ?? '(none)'}`,\n `Priority: ${n.priority} \u00B7 Est: ${n.estimateHours ?? 0}h \u00B7 Tags: ${(n.tags ?? []).join(', ') || 'none'}`,\n `Progress: ${progress.completed}/${progress.total} tasks (${progress.percentComplete}%)`,\n ].join('\\n');\n}\n\n/**\n * Resolve a `/sdd done|skip|fail|review <N|title>` argument to a single task\n * node. Tries a 1-based index into the given (already filtered/sorted) node\n * list first, then a bidirectional case-insensitive title-substring match.\n * Centralizes the matching the four mutation subcommands used to duplicate.\n */\nexport function matchTaskNode<T extends { id: string; title: string }>(\n nodes: T[],\n query: string,\n): T | null {\n const trimmed = query.trim();\n if (!trimmed) return null;\n const num = Number(trimmed);\n if (!Number.isNaN(num) && num >= 1 && num <= nodes.length) {\n return nodes[num - 1] ?? null;\n }\n const q = trimmed.toLowerCase();\n return (\n nodes.find((n) => n.title.toLowerCase().includes(q) || q.includes(n.title.toLowerCase())) ??\n null\n );\n}\n\nexport function markTaskCompleted(taskTitle: string): boolean {\n const tracker = sddState.getTaskTracker();\n if (!tracker) return false;\n const nodes = tracker.getAllNodes({ status: ['pending', 'in_progress'] });\n const match = nodes.find(\n (n) =>\n n.title.toLowerCase().includes(taskTitle.toLowerCase()) ||\n taskTitle.toLowerCase().includes(n.title.toLowerCase()),\n );\n if (!match) return false;\n tracker.updateNodeStatus(match.id, 'completed');\n return true;\n}\n\nexport function getTaskGraphId(): string | null {\n return sddState.getTaskGraphId();\n}\nexport function getTaskTrackerExport(): TaskTracker | null {\n return sddState.getTaskTracker();\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|