issue-flow 0.11.1 → 0.12.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/{analyze-XV3K25K3.js → analyze-OEET7KGE.js} +15 -10
- package/dist/{analyze-XV3K25K3.js.map → analyze-OEET7KGE.js.map} +1 -1
- package/dist/{apply-WUJHDD7A.js → apply-ZIA3C52S.js} +6 -4
- package/dist/{apply-WUJHDD7A.js.map → apply-ZIA3C52S.js.map} +1 -1
- package/dist/{chunk-6N7LZZ5U.js → chunk-3QHK4YQA.js} +204 -107
- package/dist/chunk-3QHK4YQA.js.map +1 -0
- package/dist/chunk-42PJFMAR.js +605 -0
- package/dist/chunk-42PJFMAR.js.map +1 -0
- package/dist/{chunk-EWDJENFW.js → chunk-4JSOYE4G.js} +77 -12
- package/dist/chunk-4JSOYE4G.js.map +1 -0
- package/dist/chunk-56KS3TFQ.js +703 -0
- package/dist/chunk-56KS3TFQ.js.map +1 -0
- package/dist/{chunk-AAY6KM7N.js → chunk-73ATPIYX.js} +160 -154
- package/dist/chunk-73ATPIYX.js.map +1 -0
- package/dist/{chunk-KQ4JQN7T.js → chunk-A4SLHOW7.js} +68 -12
- package/dist/chunk-A4SLHOW7.js.map +1 -0
- package/dist/chunk-BAUPQXPE.js +173 -0
- package/dist/chunk-BAUPQXPE.js.map +1 -0
- package/dist/{chunk-HC2MT7BX.js → chunk-BYZDPR7N.js} +14 -581
- package/dist/chunk-BYZDPR7N.js.map +1 -0
- package/dist/chunk-CD5ULRVC.js +615 -0
- package/dist/chunk-CD5ULRVC.js.map +1 -0
- package/dist/chunk-DIDUVL6O.js +1365 -0
- package/dist/chunk-DIDUVL6O.js.map +1 -0
- package/dist/{chunk-47VIN33V.js → chunk-EZATO3DW.js} +7 -5
- package/dist/{chunk-47VIN33V.js.map → chunk-EZATO3DW.js.map} +1 -1
- package/dist/{chunk-BXCPXRBR.js → chunk-FADTCQGM.js} +46 -11
- package/dist/chunk-FADTCQGM.js.map +1 -0
- package/dist/{chunk-GANSDJV7.js → chunk-FJP3UQGD.js} +15 -13
- package/dist/chunk-FJP3UQGD.js.map +1 -0
- package/dist/chunk-KMMASOA6.js +134 -0
- package/dist/chunk-KMMASOA6.js.map +1 -0
- package/dist/{chunk-HQYXVIH6.js → chunk-LAFNLLQP.js} +3 -3
- package/dist/chunk-LWRSMTTZ.js +18 -0
- package/dist/chunk-LWRSMTTZ.js.map +1 -0
- package/dist/{chunk-IVHZEYAX.js → chunk-M5UCXTIT.js} +13 -11
- package/dist/{chunk-IVHZEYAX.js.map → chunk-M5UCXTIT.js.map} +1 -1
- package/dist/chunk-M66SJHQQ.js +206 -0
- package/dist/chunk-M66SJHQQ.js.map +1 -0
- package/dist/{chunk-OBLVLIUS.js → chunk-MJULIKQU.js} +42 -6
- package/dist/chunk-MJULIKQU.js.map +1 -0
- package/dist/{chunk-RKTEV6EG.js → chunk-NS6R37II.js} +4 -4
- package/dist/{run-DBF2VWUN.js → chunk-NTMMF5UD.js} +515 -544
- package/dist/chunk-NTMMF5UD.js.map +1 -0
- package/dist/{chunk-EY6WMHV2.js → chunk-QCPGHBSZ.js} +14 -12
- package/dist/{chunk-EY6WMHV2.js.map → chunk-QCPGHBSZ.js.map} +1 -1
- package/dist/{chunk-LVKQ2AKT.js → chunk-TSOLWK7Y.js} +8 -6
- package/dist/chunk-TSOLWK7Y.js.map +1 -0
- package/dist/{chunk-34WNTK3K.js → chunk-XQUSIEKA.js} +11 -9
- package/dist/{chunk-34WNTK3K.js.map → chunk-XQUSIEKA.js.map} +1 -1
- package/dist/chunk-Y5ZDJ4PJ.js +76 -0
- package/dist/chunk-Y5ZDJ4PJ.js.map +1 -0
- package/dist/cli.js +156 -26
- package/dist/cli.js.map +1 -1
- package/dist/{config-4THHOXNN.js → config-PRJGWW7J.js} +15 -3
- package/dist/execute-XBNVL753.js +18 -0
- package/dist/{generate-TBVTLMOP.js → generate-EGB4KV2B.js} +16 -10
- package/dist/{generate-TBVTLMOP.js.map → generate-EGB4KV2B.js.map} +1 -1
- package/dist/init-AJHCXJBP.js +10 -0
- package/dist/operations-BYT626KQ.js +365 -0
- package/dist/operations-BYT626KQ.js.map +1 -0
- package/dist/plan-QOZ3RIO6.js +21 -0
- package/dist/{policy-XQW5NUIE.js → policy-6S4GFC76.js} +6 -4
- package/dist/{policy-XQW5NUIE.js.map → policy-6S4GFC76.js.map} +1 -1
- package/dist/pr-EBBHD2ZX.js +26 -0
- package/dist/pr-review-VVMW5PO4.js +20 -0
- package/dist/prd-JQSZF7PR.js +21 -0
- package/dist/resume-PPCNQVVV.js +221 -0
- package/dist/resume-PPCNQVVV.js.map +1 -0
- package/dist/review-3F6MARWH.js +19 -0
- package/dist/run-RXE5GDQO.js +36 -0
- package/dist/run-RXE5GDQO.js.map +1 -0
- package/dist/state-manager-2EDVDBAR.js +34 -0
- package/dist/state-manager-2EDVDBAR.js.map +1 -0
- package/dist/{web-ANQQTTRD.js → web-EF5WIIGG.js} +13 -8
- package/dist/{web-ANQQTTRD.js.map → web-EF5WIIGG.js.map} +1 -1
- package/package.json +1 -1
- package/prompts/pr.md +11 -4
- package/web/public/app.js +12 -0
- package/dist/chunk-3BOHTH4V.js +0 -58
- package/dist/chunk-3BOHTH4V.js.map +0 -1
- package/dist/chunk-6N7LZZ5U.js.map +0 -1
- package/dist/chunk-AAY6KM7N.js.map +0 -1
- package/dist/chunk-BXCPXRBR.js.map +0 -1
- package/dist/chunk-EWDJENFW.js.map +0 -1
- package/dist/chunk-GANSDJV7.js.map +0 -1
- package/dist/chunk-HC2MT7BX.js.map +0 -1
- package/dist/chunk-HDV3YDTG.js +0 -52
- package/dist/chunk-HDV3YDTG.js.map +0 -1
- package/dist/chunk-KQ4JQN7T.js.map +0 -1
- package/dist/chunk-LVKQ2AKT.js.map +0 -1
- package/dist/chunk-OBLVLIUS.js.map +0 -1
- package/dist/chunk-VQ5VN7DF.js +0 -1018
- package/dist/chunk-VQ5VN7DF.js.map +0 -1
- package/dist/execute-5OEKLM5X.js +0 -15
- package/dist/init-PRMOTQ7N.js +0 -9
- package/dist/plan-Y2BYZD24.js +0 -17
- package/dist/pr-H6GLOSXF.js +0 -21
- package/dist/pr-review-CK5FR6WX.js +0 -17
- package/dist/prd-4PWLCGH5.js +0 -17
- package/dist/review-QGVAHEZF.js +0 -15
- package/dist/run-DBF2VWUN.js.map +0 -1
- /package/dist/{chunk-HQYXVIH6.js.map → chunk-LAFNLLQP.js.map} +0 -0
- /package/dist/{chunk-RKTEV6EG.js.map → chunk-NS6R37II.js.map} +0 -0
- /package/dist/{config-4THHOXNN.js.map → config-PRJGWW7J.js.map} +0 -0
- /package/dist/{execute-5OEKLM5X.js.map → execute-XBNVL753.js.map} +0 -0
- /package/dist/{init-PRMOTQ7N.js.map → init-AJHCXJBP.js.map} +0 -0
- /package/dist/{plan-Y2BYZD24.js.map → plan-QOZ3RIO6.js.map} +0 -0
- /package/dist/{pr-H6GLOSXF.js.map → pr-EBBHD2ZX.js.map} +0 -0
- /package/dist/{pr-review-CK5FR6WX.js.map → pr-review-VVMW5PO4.js.map} +0 -0
- /package/dist/{prd-4PWLCGH5.js.map → prd-JQSZF7PR.js.map} +0 -0
- /package/dist/{review-QGVAHEZF.js.map → review-3F6MARWH.js.map} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/storage/lock.ts"],"sourcesContent":["import { mkdir, readFile, rm, writeFile } from 'node:fs/promises';\nimport { hostname } from 'node:os';\nimport { dirname } from 'node:path';\nimport { type RunLock, runLockSchema } from './schemas.js';\n\n/**\n * Run ownership: who is executing in this project right now.\n *\n * Generalised from `web/lock.ts`, which has guarded the monitoring server the\n * same way since it existed — atomic `wx` create, `process.kill(pid, 0)` for\n * liveness, and a read that degrades to \"no lock\" on anything malformed. What\n * this module adds is the piece a *long* run needs and a server does not: a\n * **heartbeat**. A web server proves it is alive by answering a health probe;\n * a pipeline has no port to probe, so it says so in the file instead.\n *\n * The two signals answer different questions and both are needed:\n *\n * - the **pid** catches the machine that rebooted or the process that was\n * `kill -9`ed — the lock outlives its owner, and nothing else would notice;\n * - the **heartbeat** catches the owner that is technically alive but no longer\n * running (a pid reused by an unrelated process is the same case), and it is\n * the only signal that survives being read from another host.\n *\n * Nothing here is destructive by inference: a lock is only taken over when it\n * is stale by both measures the policy states, and taking one over is reported\n * to the caller (`reclaimedFrom`) so the run can be recorded as interrupted\n * rather than silently restarted.\n */\n\n/** The lock is rewritten this often while a run holds it. */\nexport const RUN_LOCK_HEARTBEAT_MS = 10_000;\n\n/**\n * How many missed heartbeats make a lock stale.\n *\n * Three, not one: a machine under load, a suspended laptop or a slow\n * filesystem can miss a beat without the run being dead, and stealing a lock\n * from a live owner is the failure this module exists to prevent.\n */\nexport const RUN_LOCK_STALE_INTERVALS = 3;\n\n/** How many times a claim is retried after clearing a stale lock. */\nconst CLAIM_RETRIES = 3;\n\nexport interface RunLockHandle {\n readonly lock: RunLock;\n /**\n * The dead owner this lock was taken from, when it was taken from one.\n *\n * `null` on a clean acquisition. Non-null is the fact a caller records as an\n * *interrupted* run: something was executing here and never finished.\n */\n readonly reclaimedFrom: RunLock | null;\n /** Stop the heartbeat and remove the lock. Never rejects. */\n release(): Promise<void>;\n}\n\nexport type AcquireRunLockResult =\n | { ok: true; handle: RunLockHandle }\n | { ok: false; owner: RunLock };\n\nexport interface AcquireRunLockOptions {\n /** Issue (or queue) identifier this run works on. */\n target: string;\n /** Rewrite interval. Also the unit `staleIntervals` counts. */\n heartbeatMs?: number;\n staleIntervals?: number;\n /** Injectable identity, so a test can pretend to be another process. */\n pid?: number;\n host?: string;\n /** Injectable clock, so staleness is testable without waiting. */\n clock?: () => number;\n /** Called once, when a stale lock is taken over. Never throws through. */\n onTakeover?: (previous: RunLock) => void;\n /**\n * Start the rewriting timer. Default true; a test that drives the clock by\n * hand turns it off and calls `touchRunLock` itself.\n */\n heartbeat?: boolean;\n}\n\n/**\n * `process.kill(pid, 0)` throws `ESRCH` when the pid is gone and `EPERM` when\n * it is alive but owned by another user — **only `ESRCH` means \"not alive\"**.\n * Reading `EPERM` as death is how a lock gets stolen from a healthy run\n * started by another user.\n */\nexport function isProcessAlive(pid: number): boolean {\n try {\n process.kill(pid, 0);\n return true;\n } catch (err) {\n return (err as NodeJS.ErrnoException).code === 'EPERM';\n }\n}\n\n/**\n * Read a lock, degrading anything unreadable to \"no lock\".\n *\n * A partial write, a file from an incompatible release, a truncated JSON: all\n * of them mean the same thing operationally, and none of them justifies\n * failing a run. Same behaviour as `readWebLock` and `loadGlobalConfig`.\n */\nexport async function readRunLock(lockFile: string): Promise<RunLock | null> {\n let raw: string;\n try {\n raw = await readFile(lockFile, 'utf-8');\n } catch {\n return null;\n }\n try {\n const result = runLockSchema.safeParse(JSON.parse(raw));\n return result.success ? result.data : null;\n } catch {\n return null;\n }\n}\n\n/** Remove a lock file. Best effort; never rejects. */\nexport async function removeRunLock(lockFile: string): Promise<void> {\n try {\n await rm(lockFile, { force: true });\n } catch {\n /* best-effort cleanup */\n }\n}\n\nexport interface StalenessOptions {\n heartbeatMs?: number;\n staleIntervals?: number;\n clock?: () => number;\n /** Treat this pid as our own, so a re-entrant claim is not \"stale\". */\n pid?: number;\n host?: string;\n}\n\n/**\n * Whether `lock` may be taken over.\n *\n * A lock written on another host cannot be judged by pid — `process.kill` there\n * says nothing about a process here — so on a foreign host only the heartbeat\n * decides. That is the case a shared filesystem creates, and reading it wrong\n * is how two machines end up on the same branch.\n */\nexport function isRunLockStale(lock: RunLock, options: StalenessOptions = {}): boolean {\n const heartbeatMs = options.heartbeatMs ?? RUN_LOCK_HEARTBEAT_MS;\n const staleIntervals = options.staleIntervals ?? RUN_LOCK_STALE_INTERVALS;\n const now = (options.clock ?? Date.now)();\n const host = options.host ?? hostname();\n\n const beat = Date.parse(lock.lastHeartbeatAt);\n const silent = Number.isNaN(beat) || now - beat > heartbeatMs * staleIntervals;\n\n if (lock.host !== host) return silent;\n return silent || !isProcessAlive(lock.pid);\n}\n\n/** Whether this very process already owns `lock`. */\nfunction isSelf(lock: RunLock, pid: number, host: string): boolean {\n return lock.pid === pid && lock.host === host;\n}\n\n/**\n * One line naming the owner, for the message a refused invocation prints.\n *\n * The pid alone is useless to a person; the pid *plus the host plus when it\n * last said anything* is what lets them decide between waiting and killing it.\n */\nexport function describeRunLockOwner(lock: RunLock): string {\n return `pid ${lock.pid} on ${lock.host} (issue ${lock.target}, started ${lock.startedAt}, last heartbeat ${lock.lastHeartbeatAt})`;\n}\n\n/** Rewrite the lock with a fresh heartbeat. Never rejects. */\nexport async function touchRunLock(lockFile: string, lock: RunLock, at: string): Promise<RunLock> {\n const next: RunLock = { ...lock, lastHeartbeatAt: at };\n try {\n await writeFile(lockFile, `${JSON.stringify(next, null, 2)}\\n`, 'utf-8');\n } catch {\n // A heartbeat that cannot be written is not worth failing a run over: the\n // worst case is that another invocation eventually judges us stale, which\n // is the same outcome as this process having died.\n }\n return next;\n}\n\n/**\n * Atomic exclusive create. `wx` fails with `EEXIST` when someone else won the\n * race, which is what lets two concurrent invocations agree on one winner\n * without a lock for the lock.\n */\nasync function claim(lockFile: string, lock: RunLock): Promise<boolean> {\n try {\n await mkdir(dirname(lockFile), { recursive: true });\n await writeFile(lockFile, `${JSON.stringify(lock, null, 2)}\\n`, {\n encoding: 'utf-8',\n flag: 'wx',\n });\n return true;\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'EEXIST') return false;\n throw err;\n }\n}\n\n/**\n * Take ownership of the run in this project, or report who already has it.\n *\n * Three outcomes, and only three:\n *\n * 1. **free** — the lock is claimed and a heartbeat starts;\n * 2. **held by a dead owner** — the stale lock is cleared, claimed, and the\n * previous owner comes back on `reclaimedFrom` so the caller can record the\n * interrupted run;\n * 3. **held by a live owner** — `{ ok: false, owner }`, and the caller refuses\n * with a message naming it. Nothing is deleted, nothing is retried into\n * submission.\n *\n * A lock this very process already holds is outcome 1 with a no-op release:\n * re-entering is not a conflict, and a nested acquisition must not remove the\n * file the outer one still owns.\n */\nexport async function acquireRunLock(\n lockFile: string,\n options: AcquireRunLockOptions,\n): Promise<AcquireRunLockResult> {\n const pid = options.pid ?? process.pid;\n const host = options.host ?? hostname();\n const heartbeatMs = options.heartbeatMs ?? RUN_LOCK_HEARTBEAT_MS;\n const clock = options.clock ?? Date.now;\n const staleness: StalenessOptions = {\n heartbeatMs,\n ...(options.staleIntervals === undefined ? {} : { staleIntervals: options.staleIntervals }),\n clock,\n pid,\n host,\n };\n\n let reclaimedFrom: RunLock | null = null;\n\n for (let attempt = 0; attempt <= CLAIM_RETRIES; attempt++) {\n const at = new Date(clock()).toISOString();\n const mine: RunLock = {\n pid,\n host,\n target: options.target,\n startedAt: at,\n lastHeartbeatAt: at,\n };\n\n if (await claim(lockFile, mine)) {\n return { ok: true, handle: startHandle(lockFile, mine, reclaimedFrom, options) };\n }\n\n const existing = await readRunLock(lockFile);\n\n if (existing === null) {\n // Unreadable: a partial write, or a file from a shape we do not know.\n // Treated as absent, exactly as `readRunLock` documents.\n await removeRunLock(lockFile);\n continue;\n }\n\n if (isSelf(existing, pid, host)) {\n return { ok: true, handle: nestedHandle(existing) };\n }\n\n if (!isRunLockStale(existing, staleness)) {\n return { ok: false, owner: existing };\n }\n\n reclaimedFrom = existing;\n try {\n options.onTakeover?.(existing);\n } catch {\n // A caller's bookkeeping must not decide whether the run starts.\n }\n await removeRunLock(lockFile);\n }\n\n // Every attempt lost the race and every lock read was stale or unreadable:\n // something else is fighting for this file. Refusing is the safe answer.\n const owner = await readRunLock(lockFile);\n if (owner !== null) return { ok: false, owner };\n return {\n ok: false,\n owner: {\n pid: 0,\n host,\n target: options.target,\n startedAt: new Date(clock()).toISOString(),\n lastHeartbeatAt: new Date(clock()).toISOString(),\n },\n };\n}\n\nfunction startHandle(\n lockFile: string,\n lock: RunLock,\n reclaimedFrom: RunLock | null,\n options: AcquireRunLockOptions,\n): RunLockHandle {\n const heartbeatMs = options.heartbeatMs ?? RUN_LOCK_HEARTBEAT_MS;\n const clock = options.clock ?? Date.now;\n let current = lock;\n let timer: NodeJS.Timeout | null = null;\n\n if (options.heartbeat !== false) {\n timer = setInterval(() => {\n void touchRunLock(lockFile, current, new Date(clock()).toISOString()).then((next) => {\n current = next;\n });\n }, heartbeatMs);\n // The heartbeat must never be the reason a process stays alive.\n timer.unref();\n }\n\n return {\n lock,\n reclaimedFrom,\n release: async () => {\n if (timer !== null) {\n clearInterval(timer);\n timer = null;\n }\n await removeRunLock(lockFile);\n },\n };\n}\n\n/** A re-entrant acquisition: same owner, no timer of its own, no removal. */\nfunction nestedHandle(lock: RunLock): RunLockHandle {\n return { lock, reclaimedFrom: null, release: async () => {} };\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,332 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
var policyDiscoveryConfigSchema = z.object({
|
|
6
|
-
issueTemplates: z.boolean().default(true),
|
|
7
|
-
pullRequestTemplate: z.boolean().default(true),
|
|
8
|
-
docs: z.boolean().default(true),
|
|
9
|
-
codeowners: z.boolean().default(true),
|
|
10
|
-
labels: z.boolean().default(true),
|
|
11
|
-
issueTypes: z.boolean().default(true)
|
|
12
|
-
});
|
|
13
|
-
var policyIssuesDeclarationSchema = z.object({
|
|
14
|
-
titleConvention: z.string().min(1).optional(),
|
|
15
|
-
/**
|
|
16
|
-
* Whether Issue Flow may create a label the repository does not have.
|
|
17
|
-
*
|
|
18
|
-
* `.optional()` rather than `.default(false)`: the resolved declarations must
|
|
19
|
-
* stay "only what was written", or the group would always look declared and
|
|
20
|
-
* `sources` would report a provenance nobody asked for. The effective default
|
|
21
|
-
* — false — lives at the call site.
|
|
22
|
-
*
|
|
23
|
-
* That default is a deliberate change of behaviour: creating taxonomy without
|
|
24
|
-
* asking is the defect this exists to stop. A repository that wants the old
|
|
25
|
-
* behaviour opts back into it.
|
|
26
|
-
*/
|
|
27
|
-
allowLabelCreation: z.boolean().optional()
|
|
28
|
-
});
|
|
29
|
-
var policyPullRequestsDeclarationSchema = z.object({
|
|
30
|
-
baseBranch: z.string().min(1).optional(),
|
|
31
|
-
titleConvention: z.string().min(1).optional()
|
|
32
|
-
});
|
|
33
|
-
var policyGitDeclarationSchema = z.object({
|
|
34
|
-
branchConvention: z.string().min(1).optional(),
|
|
35
|
-
commitConvention: z.string().min(1).optional()
|
|
36
|
-
});
|
|
37
|
-
var policyConfigSchema = z.object({
|
|
38
|
-
enabled: z.boolean().default(true),
|
|
39
|
-
/**
|
|
40
|
-
* Token budget for the `__REPO_POLICY__` projection. Sections above it are
|
|
41
|
-
* replaced whole by a pointer rather than truncated — see
|
|
42
|
-
* `policy/placeholders.ts`.
|
|
43
|
-
*/
|
|
44
|
-
contextBudget: z.number().int().positive().default(1500),
|
|
45
|
-
discovery: policyDiscoveryConfigSchema.prefault({}),
|
|
46
|
-
issues: policyIssuesDeclarationSchema.prefault({}),
|
|
47
|
-
pullRequests: policyPullRequestsDeclarationSchema.prefault({}),
|
|
48
|
-
git: policyGitDeclarationSchema.prefault({})
|
|
49
|
-
});
|
|
50
|
-
var policyConfigInputSchema = z.object({
|
|
51
|
-
enabled: z.boolean().optional(),
|
|
52
|
-
contextBudget: z.number().int().positive().optional(),
|
|
53
|
-
// Spelled out rather than `policyDiscoveryConfigSchema.partial()`: in zod 4
|
|
54
|
-
// a `.default()` survives `.partial()`, so every toggle would come back
|
|
55
|
-
// materialized and the input layer would stop meaning "what the user wrote".
|
|
56
|
-
discovery: z.object({
|
|
57
|
-
issueTemplates: z.boolean().optional(),
|
|
58
|
-
pullRequestTemplate: z.boolean().optional(),
|
|
59
|
-
docs: z.boolean().optional(),
|
|
60
|
-
codeowners: z.boolean().optional(),
|
|
61
|
-
labels: z.boolean().optional(),
|
|
62
|
-
issueTypes: z.boolean().optional()
|
|
63
|
-
}).optional(),
|
|
64
|
-
issues: z.object({ titleConvention: z.string().nullish(), allowLabelCreation: z.boolean().optional() }).optional(),
|
|
65
|
-
pullRequests: z.object({ baseBranch: z.string().nullish(), titleConvention: z.string().nullish() }).optional(),
|
|
66
|
-
git: z.object({ branchConvention: z.string().nullish(), commitConvention: z.string().nullish() }).optional()
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
// src/schemas.ts
|
|
70
|
-
import { z as z2 } from "zod";
|
|
71
|
-
var claudeUsageSchema = z2.object({
|
|
72
|
-
inputTokens: z2.number().optional(),
|
|
73
|
-
outputTokens: z2.number().optional(),
|
|
74
|
-
cacheReadTokens: z2.number().optional(),
|
|
75
|
-
cacheCreationTokens: z2.number().optional(),
|
|
76
|
-
costUsd: z2.number().optional()
|
|
77
|
-
});
|
|
78
|
-
var userStoryStatusSchema = z2.enum(["backlog", "in_progress", "in_review", "done"]);
|
|
79
|
-
var storyStageSchema = z2.enum([
|
|
80
|
-
"pending",
|
|
81
|
-
"executing",
|
|
82
|
-
"awaiting_review",
|
|
83
|
-
"in_review",
|
|
84
|
-
"in_correction",
|
|
85
|
-
"done",
|
|
86
|
-
"failed"
|
|
87
|
-
]);
|
|
88
|
-
var userStorySchema = z2.object({
|
|
89
|
-
id: z2.string(),
|
|
90
|
-
title: z2.string(),
|
|
91
|
-
description: z2.string(),
|
|
92
|
-
acceptanceCriteria: z2.array(z2.string()),
|
|
93
|
-
priority: z2.number().int().positive(),
|
|
94
|
-
passes: z2.boolean(),
|
|
95
|
-
notes: z2.string(),
|
|
96
|
-
...claudeUsageSchema.shape,
|
|
97
|
-
durationSeconds: z2.number().optional(),
|
|
98
|
-
status: userStoryStatusSchema.optional(),
|
|
99
|
-
dependencies: z2.array(z2.string()).optional(),
|
|
100
|
-
stage: storyStageSchema.optional(),
|
|
101
|
-
stageSince: z2.string().optional(),
|
|
102
|
-
stageDetail: z2.string().optional()
|
|
103
|
-
});
|
|
104
|
-
var pipelineStateSchema = z2.object({
|
|
105
|
-
analyzeCompleted: z2.boolean().optional(),
|
|
106
|
-
prdCompleted: z2.boolean(),
|
|
107
|
-
jsonCompleted: z2.boolean(),
|
|
108
|
-
executionCompleted: z2.boolean(),
|
|
109
|
-
reviewCompleted: z2.boolean(),
|
|
110
|
-
prCreated: z2.boolean(),
|
|
111
|
-
prReviewCompleted: z2.boolean().optional()
|
|
112
|
-
});
|
|
113
|
-
var pullRequestRefSchema = z2.object({
|
|
114
|
-
number: z2.number().int().positive(),
|
|
115
|
-
url: z2.string(),
|
|
116
|
-
headBranch: z2.string(),
|
|
117
|
-
createdAt: z2.string()
|
|
118
|
-
});
|
|
119
|
-
var prReviewRecommendationSchema = z2.enum([
|
|
120
|
-
"APPROVE",
|
|
121
|
-
"APPROVE_WITH_SUGGESTIONS",
|
|
122
|
-
"REQUEST_CHANGES"
|
|
123
|
-
]);
|
|
124
|
-
var prReviewStateSchema = z2.object({
|
|
125
|
-
enabled: z2.boolean().default(false),
|
|
126
|
-
pullRequestNumber: z2.number().int().positive().optional(),
|
|
127
|
-
rounds: z2.number().int().min(0).default(0),
|
|
128
|
-
lastRecommendation: prReviewRecommendationSchema.optional(),
|
|
129
|
-
lastReviewedAt: z2.string().optional()
|
|
130
|
-
});
|
|
131
|
-
var lastErrorSchema = z2.object({
|
|
132
|
-
category: z2.string(),
|
|
133
|
-
message: z2.string(),
|
|
134
|
-
at: z2.string()
|
|
135
|
-
});
|
|
136
|
-
var issueMetadataSchema = z2.object({
|
|
137
|
-
schemaVersion: z2.literal(1),
|
|
138
|
-
id: z2.string().min(1),
|
|
139
|
-
number: z2.number().int().positive().nullable(),
|
|
140
|
-
source: z2.enum(["github", "local"]),
|
|
141
|
-
title: z2.string(),
|
|
142
|
-
labels: z2.array(z2.string()),
|
|
143
|
-
state: z2.enum(["open", "closed"]),
|
|
144
|
-
createdAt: z2.string(),
|
|
145
|
-
updatedAt: z2.string(),
|
|
146
|
-
contentHash: z2.string(),
|
|
147
|
-
remote: z2.object({
|
|
148
|
-
provider: z2.enum(["github", "local"]),
|
|
149
|
-
ref: z2.string(),
|
|
150
|
-
syncedAt: z2.string(),
|
|
151
|
-
syncedContentHash: z2.string()
|
|
152
|
-
}).optional()
|
|
153
|
-
});
|
|
154
|
-
var taskPlanSchema = z2.object({
|
|
155
|
-
project: z2.string(),
|
|
156
|
-
issueNumber: z2.union([z2.number().int().positive(), z2.string().min(1)]),
|
|
157
|
-
issueUrl: z2.string().optional().default(""),
|
|
158
|
-
branchName: z2.string(),
|
|
159
|
-
noBranch: z2.boolean().optional().default(false),
|
|
160
|
-
description: z2.string(),
|
|
161
|
-
issueStatus: z2.enum(["pending", "in_progress", "completed"]),
|
|
162
|
-
completedAt: z2.string().nullable(),
|
|
163
|
-
lastAttemptAt: z2.string().nullable(),
|
|
164
|
-
lastError: lastErrorSchema.nullable(),
|
|
165
|
-
correctionCycle: z2.number().int().min(0),
|
|
166
|
-
maxCorrectionCycles: z2.number().int().min(0),
|
|
167
|
-
/**
|
|
168
|
-
* Findings from the most recent failed review, verbatim. Non-null means the
|
|
169
|
-
* issue has a pending correction even if every userStories[].passes is
|
|
170
|
-
* already true — the execute phase must address these before the field is
|
|
171
|
-
* cleared back to null. See core/engine.ts's early-return guards.
|
|
172
|
-
*/
|
|
173
|
-
lastReviewFindings: z2.string().nullable().optional().default(null),
|
|
174
|
-
pipeline: pipelineStateSchema,
|
|
175
|
-
pullRequest: pullRequestRefSchema.optional(),
|
|
176
|
-
prReview: prReviewStateSchema.optional(),
|
|
177
|
-
userStories: z2.array(userStorySchema)
|
|
178
|
-
});
|
|
179
|
-
var headlessResultSchema = z2.object({
|
|
180
|
-
success: z2.boolean(),
|
|
181
|
-
result: z2.string(),
|
|
182
|
-
cost: claudeUsageSchema.nullable(),
|
|
183
|
-
error: z2.string().nullable()
|
|
184
|
-
});
|
|
185
|
-
var sessionLogEntrySchema = z2.object({
|
|
186
|
-
at: z2.string(),
|
|
187
|
-
level: z2.enum(["info", "warn", "error"]),
|
|
188
|
-
message: z2.string()
|
|
189
|
-
});
|
|
190
|
-
var sessionUsageShape = {
|
|
191
|
-
inputTokens: z2.number().nullable().default(null),
|
|
192
|
-
outputTokens: z2.number().nullable().default(null),
|
|
193
|
-
cacheReadTokens: z2.number().nullable().default(null),
|
|
194
|
-
cacheCreationTokens: z2.number().nullable().default(null),
|
|
195
|
-
costUsd: z2.number().nullable().default(null)
|
|
196
|
-
};
|
|
197
|
-
var sessionPhaseSchema = z2.object({
|
|
198
|
-
name: z2.string(),
|
|
199
|
-
status: z2.enum(["pending", "running", "completed", "failed"]),
|
|
200
|
-
startedAt: z2.string().nullable(),
|
|
201
|
-
endedAt: z2.string().nullable(),
|
|
202
|
-
durationSeconds: z2.number().nullable(),
|
|
203
|
-
error: z2.string().nullable(),
|
|
204
|
-
...sessionUsageShape
|
|
205
|
-
});
|
|
206
|
-
var sessionStorySchema = z2.object({
|
|
207
|
-
id: z2.string(),
|
|
208
|
-
title: z2.string(),
|
|
209
|
-
priority: z2.number(),
|
|
210
|
-
passes: z2.boolean(),
|
|
211
|
-
completedAt: z2.string().nullable(),
|
|
212
|
-
// Also introduced with the metrics, hence the same tolerant default.
|
|
213
|
-
durationSeconds: z2.number().nullable().default(null),
|
|
214
|
-
// Snapshot fields are always present on output and defaulted on input, so a
|
|
215
|
-
// session.json written before they existed still parses. This is the mirror
|
|
216
|
-
// image of userStorySchema, where the same two fields are plainly optional.
|
|
217
|
-
status: userStoryStatusSchema.default("backlog"),
|
|
218
|
-
dependencies: z2.array(z2.string()).default([]),
|
|
219
|
-
// Published for the panel's story detail view. Same tolerant default as the
|
|
220
|
-
// fields above: absent (older session.json) and empty resolve to the same
|
|
221
|
-
// value, so the client never has to tell them apart.
|
|
222
|
-
description: z2.string().default(""),
|
|
223
|
-
acceptanceCriteria: z2.array(z2.string()).default([]),
|
|
224
|
-
// Additive like the rest of this schema: a session.json written before
|
|
225
|
-
// `stage` existed parses into 'pending'/null, the same values a fresh
|
|
226
|
-
// snapshot starts a story at.
|
|
227
|
-
stage: storyStageSchema.default("pending"),
|
|
228
|
-
stageSince: z2.string().nullable().default(null),
|
|
229
|
-
stageDetail: z2.string().nullable().default(null),
|
|
230
|
-
...sessionUsageShape
|
|
231
|
-
});
|
|
232
|
-
var sessionSnapshotSchema = z2.object({
|
|
233
|
-
schemaVersion: z2.literal(1),
|
|
234
|
-
sessionId: z2.string().nullable(),
|
|
235
|
-
readOnly: z2.literal(true),
|
|
236
|
-
capabilities: z2.array(z2.string()),
|
|
237
|
-
issue: z2.object({
|
|
238
|
-
number: z2.number().nullable(),
|
|
239
|
-
url: z2.string().nullable(),
|
|
240
|
-
// Additive: a session.json written before the Issue section was enriched
|
|
241
|
-
// parses into the same "not reported" values createInitialSnapshot() uses.
|
|
242
|
-
title: z2.string().nullable().default(null),
|
|
243
|
-
description: z2.string().nullable().default(null),
|
|
244
|
-
labels: z2.array(z2.string()).default([]),
|
|
245
|
-
state: z2.string().nullable().default(null)
|
|
246
|
-
}),
|
|
247
|
-
status: z2.enum(["idle", "running", "completed", "failed"]),
|
|
248
|
-
startedAt: z2.string().nullable(),
|
|
249
|
-
updatedAt: z2.string().nullable(),
|
|
250
|
-
endedAt: z2.string().nullable(),
|
|
251
|
-
elapsedSeconds: z2.number().nullable(),
|
|
252
|
-
estimatedRemainingSeconds: z2.number().nullable(),
|
|
253
|
-
progress: z2.object({
|
|
254
|
-
percent: z2.number(),
|
|
255
|
-
phasesCompleted: z2.number(),
|
|
256
|
-
phasesTotal: z2.number(),
|
|
257
|
-
storiesCompleted: z2.number(),
|
|
258
|
-
storiesTotal: z2.number()
|
|
259
|
-
}),
|
|
260
|
-
currentPhase: z2.string().nullable(),
|
|
261
|
-
currentActivity: z2.object({
|
|
262
|
-
story: z2.string().nullable(),
|
|
263
|
-
tool: z2.string().nullable(),
|
|
264
|
-
detail: z2.string().nullable(),
|
|
265
|
-
since: z2.string()
|
|
266
|
-
}).nullable(),
|
|
267
|
-
phases: z2.array(sessionPhaseSchema),
|
|
268
|
-
stories: z2.array(sessionStorySchema),
|
|
269
|
-
// The whole aggregate is additive: a snapshot from before it existed parses
|
|
270
|
-
// into the same "nothing reported" object the reducer starts from.
|
|
271
|
-
metrics: z2.object({
|
|
272
|
-
totalInputTokens: z2.number().nullable().default(null),
|
|
273
|
-
totalOutputTokens: z2.number().nullable().default(null),
|
|
274
|
-
totalCacheReadTokens: z2.number().nullable().default(null),
|
|
275
|
-
totalCacheCreationTokens: z2.number().nullable().default(null),
|
|
276
|
-
totalCostUsd: z2.number().nullable().default(null)
|
|
277
|
-
}).default({
|
|
278
|
-
totalInputTokens: null,
|
|
279
|
-
totalOutputTokens: null,
|
|
280
|
-
totalCacheReadTokens: null,
|
|
281
|
-
totalCacheCreationTokens: null,
|
|
282
|
-
totalCostUsd: null
|
|
283
|
-
}),
|
|
284
|
-
execution: z2.object({
|
|
285
|
-
iteration: z2.number(),
|
|
286
|
-
retries: z2.number(),
|
|
287
|
-
correctionCycle: z2.number(),
|
|
288
|
-
maxCorrectionCycles: z2.number().nullable()
|
|
289
|
-
}),
|
|
290
|
-
git: z2.object({
|
|
291
|
-
branch: z2.string().nullable(),
|
|
292
|
-
baseBranch: z2.string().nullable(),
|
|
293
|
-
commits: z2.array(z2.object({ hash: z2.string(), subject: z2.string() }))
|
|
294
|
-
}),
|
|
295
|
-
// Additive like the metrics aggregate: a session.json written before the
|
|
296
|
-
// repository section existed parses into the same all-null object
|
|
297
|
-
// createInitialSnapshot() starts from.
|
|
298
|
-
repository: z2.object({
|
|
299
|
-
name: z2.string().nullable().default(null),
|
|
300
|
-
remoteUrl: z2.string().nullable().default(null),
|
|
301
|
-
branch: z2.string().nullable().default(null),
|
|
302
|
-
headCommit: z2.string().nullable().default(null),
|
|
303
|
-
root: z2.string().nullable().default(null)
|
|
304
|
-
}).default({ name: null, remoteUrl: null, branch: null, headCommit: null, root: null }),
|
|
305
|
-
pullRequests: z2.array(z2.object({ number: z2.number(), url: z2.string(), title: z2.string() })),
|
|
306
|
-
logs: z2.array(sessionLogEntrySchema),
|
|
307
|
-
errors: z2.array(sessionLogEntrySchema),
|
|
308
|
-
warnings: z2.array(sessionLogEntrySchema),
|
|
309
|
-
lastError: z2.object({ message: z2.string(), at: z2.string() }).nullable(),
|
|
310
|
-
nextSteps: z2.array(z2.string()),
|
|
311
|
-
environment: z2.object({ node: z2.string(), platform: z2.string() }).nullable()
|
|
312
|
-
});
|
|
313
|
-
var webConfigSchema = z2.object({
|
|
314
|
-
enabled: z2.boolean().default(false),
|
|
315
|
-
port: z2.number().int().min(1).max(65535).default(3737),
|
|
316
|
-
host: z2.string().min(1).default("0.0.0.0"),
|
|
317
|
-
refreshSeconds: z2.number().positive().default(5),
|
|
318
|
-
logLimit: z2.number().int().positive().default(200),
|
|
319
|
-
includeLogs: z2.boolean().default(true)
|
|
320
|
-
});
|
|
321
|
-
var issuesConfigSchema = z2.object({
|
|
322
|
-
defaultGenerateTarget: z2.enum(["github", "local", "both"]).default("github"),
|
|
323
|
-
preferredProvider: z2.enum(["github", "local"]).default("github"),
|
|
324
|
-
conflictPolicy: z2.enum(["ask", "prefer-local", "prefer-github"]).default("ask"),
|
|
325
|
-
requireConfirmation: z2.boolean().default(true)
|
|
326
|
-
});
|
|
327
|
-
var prReviewConfigSchema = z2.object({
|
|
328
|
-
publisher: z2.enum(["local"]).default("local")
|
|
329
|
-
});
|
|
2
|
+
import {
|
|
3
|
+
isoNow
|
|
4
|
+
} from "./chunk-CD5ULRVC.js";
|
|
330
5
|
|
|
331
6
|
// src/core/session-state.ts
|
|
332
7
|
import { mkdir, rename, writeFile } from "fs/promises";
|
|
@@ -917,230 +592,6 @@ function getSessionPublisher() {
|
|
|
917
592
|
return _publisher;
|
|
918
593
|
}
|
|
919
594
|
|
|
920
|
-
// src/core/state-manager.ts
|
|
921
|
-
import { readFile } from "fs/promises";
|
|
922
|
-
import { ZodError } from "zod";
|
|
923
|
-
|
|
924
|
-
// src/utils/fs.ts
|
|
925
|
-
import { copyFile, mkdtemp, rename as rename2, unlink, writeFile as writeFile2 } from "fs/promises";
|
|
926
|
-
import { tmpdir } from "os";
|
|
927
|
-
import { basename, join } from "path";
|
|
928
|
-
async function writeFileAtomic(path, content) {
|
|
929
|
-
const tmpDir = await mkdtemp(join(tmpdir(), "issue-flow-"));
|
|
930
|
-
const tmpFile = join(tmpDir, basename(path));
|
|
931
|
-
await writeFile2(tmpFile, content, "utf-8");
|
|
932
|
-
try {
|
|
933
|
-
await rename2(tmpFile, path);
|
|
934
|
-
} catch (err) {
|
|
935
|
-
if (err.code === "EXDEV") {
|
|
936
|
-
await copyFile(tmpFile, path);
|
|
937
|
-
await unlink(tmpFile);
|
|
938
|
-
} else {
|
|
939
|
-
throw err;
|
|
940
|
-
}
|
|
941
|
-
}
|
|
942
|
-
}
|
|
943
|
-
|
|
944
|
-
// src/core/metrics.ts
|
|
945
|
-
var USAGE_FIELDS = [
|
|
946
|
-
"inputTokens",
|
|
947
|
-
"outputTokens",
|
|
948
|
-
"cacheReadTokens",
|
|
949
|
-
"cacheCreationTokens",
|
|
950
|
-
"costUsd"
|
|
951
|
-
];
|
|
952
|
-
function num(value) {
|
|
953
|
-
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
954
|
-
}
|
|
955
|
-
function isRecord(value) {
|
|
956
|
-
return typeof value === "object" && value !== null;
|
|
957
|
-
}
|
|
958
|
-
function parseUsage(payload) {
|
|
959
|
-
if (!isRecord(payload)) return null;
|
|
960
|
-
const usageRaw = isRecord(payload.usage) ? payload.usage : void 0;
|
|
961
|
-
const usage = {};
|
|
962
|
-
const inputTokens = usageRaw ? num(usageRaw.input_tokens) : void 0;
|
|
963
|
-
const outputTokens = usageRaw ? num(usageRaw.output_tokens) : void 0;
|
|
964
|
-
const cacheReadTokens = usageRaw ? num(usageRaw.cache_read_input_tokens) : void 0;
|
|
965
|
-
const cacheCreationTokens = usageRaw ? num(usageRaw.cache_creation_input_tokens) : void 0;
|
|
966
|
-
const costUsd = num(payload.total_cost_usd);
|
|
967
|
-
const legacyInput = num(payload.num_input_tokens);
|
|
968
|
-
const legacyOutput = num(payload.num_output_tokens);
|
|
969
|
-
const legacyCost = num(payload.cost_usd);
|
|
970
|
-
const resolvedInput = inputTokens ?? legacyInput;
|
|
971
|
-
const resolvedOutput = outputTokens ?? legacyOutput;
|
|
972
|
-
const resolvedCost = costUsd ?? legacyCost;
|
|
973
|
-
if (resolvedInput !== void 0) usage.inputTokens = resolvedInput;
|
|
974
|
-
if (resolvedOutput !== void 0) usage.outputTokens = resolvedOutput;
|
|
975
|
-
if (cacheReadTokens !== void 0) usage.cacheReadTokens = cacheReadTokens;
|
|
976
|
-
if (cacheCreationTokens !== void 0) usage.cacheCreationTokens = cacheCreationTokens;
|
|
977
|
-
if (resolvedCost !== void 0) usage.costUsd = resolvedCost;
|
|
978
|
-
return Object.keys(usage).length > 0 ? usage : null;
|
|
979
|
-
}
|
|
980
|
-
function sumUsage(a, b) {
|
|
981
|
-
const result = {};
|
|
982
|
-
for (const field of USAGE_FIELDS) {
|
|
983
|
-
const left = a?.[field];
|
|
984
|
-
const right = b?.[field];
|
|
985
|
-
if (left === void 0 && right === void 0) continue;
|
|
986
|
-
result[field] = (left ?? 0) + (right ?? 0);
|
|
987
|
-
}
|
|
988
|
-
return result;
|
|
989
|
-
}
|
|
990
|
-
function divideUsage(usage, parts) {
|
|
991
|
-
const result = {};
|
|
992
|
-
if (!usage) return result;
|
|
993
|
-
const divisor = Number.isFinite(parts) && parts > 1 ? parts : 1;
|
|
994
|
-
for (const field of USAGE_FIELDS) {
|
|
995
|
-
const value = usage[field];
|
|
996
|
-
if (value === void 0) continue;
|
|
997
|
-
result[field] = field === "costUsd" ? value / divisor : Math.round(value / divisor);
|
|
998
|
-
}
|
|
999
|
-
return result;
|
|
1000
|
-
}
|
|
1001
|
-
function compactTokens(value) {
|
|
1002
|
-
const abs = Math.abs(value);
|
|
1003
|
-
if (abs >= 1e6) return `${(value / 1e6).toFixed(1)}M`;
|
|
1004
|
-
if (abs >= 1e3) return `${(value / 1e3).toFixed(1)}k`;
|
|
1005
|
-
return String(Math.round(value));
|
|
1006
|
-
}
|
|
1007
|
-
function formatTokens(usage) {
|
|
1008
|
-
if (!usage) return "";
|
|
1009
|
-
const segments = [];
|
|
1010
|
-
const io = [];
|
|
1011
|
-
if (usage.inputTokens !== void 0) io.push(`${compactTokens(usage.inputTokens)} in`);
|
|
1012
|
-
if (usage.outputTokens !== void 0) io.push(`${compactTokens(usage.outputTokens)} out`);
|
|
1013
|
-
if (io.length > 0) segments.push(io.join(" / "));
|
|
1014
|
-
const cache = usage.cacheReadTokens !== void 0 || usage.cacheCreationTokens !== void 0 ? (usage.cacheReadTokens ?? 0) + (usage.cacheCreationTokens ?? 0) : void 0;
|
|
1015
|
-
if (cache !== void 0) segments.push(`${compactTokens(cache)} cache`);
|
|
1016
|
-
if (usage.costUsd !== void 0) segments.push(`~$${usage.costUsd.toFixed(4)}`);
|
|
1017
|
-
return segments.join(" \xB7 ");
|
|
1018
|
-
}
|
|
1019
|
-
function hasUsageData(usage) {
|
|
1020
|
-
if (!usage) return false;
|
|
1021
|
-
return USAGE_FIELDS.some((field) => usage[field] !== void 0);
|
|
1022
|
-
}
|
|
1023
|
-
|
|
1024
|
-
// src/core/state-manager.ts
|
|
1025
|
-
function isoNow() {
|
|
1026
|
-
return (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
1027
|
-
}
|
|
1028
|
-
async function loadTaskPlan(path) {
|
|
1029
|
-
const content = await readFile(path, "utf-8");
|
|
1030
|
-
const raw = JSON.parse(content);
|
|
1031
|
-
try {
|
|
1032
|
-
return taskPlanSchema.parse(raw);
|
|
1033
|
-
} catch (err) {
|
|
1034
|
-
if (err instanceof ZodError) {
|
|
1035
|
-
const issues = err.issues.map((i) => ` - ${i.path.join(".")}: ${i.message}`).join("\n");
|
|
1036
|
-
throw new Error(`Invalid tasks.json at ${path}:
|
|
1037
|
-
${issues}`);
|
|
1038
|
-
}
|
|
1039
|
-
throw err;
|
|
1040
|
-
}
|
|
1041
|
-
}
|
|
1042
|
-
async function saveTaskPlan(path, plan) {
|
|
1043
|
-
await writeFileAtomic(path, `${JSON.stringify(plan, null, 2)}
|
|
1044
|
-
`);
|
|
1045
|
-
}
|
|
1046
|
-
function initializeState(plan) {
|
|
1047
|
-
const defaultPipeline = {
|
|
1048
|
-
prdCompleted: false,
|
|
1049
|
-
jsonCompleted: false,
|
|
1050
|
-
executionCompleted: false,
|
|
1051
|
-
reviewCompleted: false,
|
|
1052
|
-
prCreated: false
|
|
1053
|
-
};
|
|
1054
|
-
return {
|
|
1055
|
-
...plan,
|
|
1056
|
-
issueStatus: plan.issueStatus ?? "pending",
|
|
1057
|
-
completedAt: plan.completedAt ?? null,
|
|
1058
|
-
lastAttemptAt: plan.lastAttemptAt ?? null,
|
|
1059
|
-
lastError: plan.lastError ?? null,
|
|
1060
|
-
correctionCycle: plan.correctionCycle ?? 0,
|
|
1061
|
-
maxCorrectionCycles: plan.maxCorrectionCycles ?? 3,
|
|
1062
|
-
lastReviewFindings: plan.lastReviewFindings ?? null,
|
|
1063
|
-
pipeline: plan.pipeline ? { ...defaultPipeline, ...plan.pipeline } : defaultPipeline
|
|
1064
|
-
};
|
|
1065
|
-
}
|
|
1066
|
-
function hasPendingCorrection(plan) {
|
|
1067
|
-
return Boolean(plan.lastReviewFindings);
|
|
1068
|
-
}
|
|
1069
|
-
function allStoriesPass(plan) {
|
|
1070
|
-
if (plan.userStories.length === 0) {
|
|
1071
|
-
return false;
|
|
1072
|
-
}
|
|
1073
|
-
return plan.userStories.every((story) => story.passes === true);
|
|
1074
|
-
}
|
|
1075
|
-
function selectActiveStory(stories) {
|
|
1076
|
-
return [...stories].filter((story) => !story.passes).sort((a, b) => a.priority - b.priority)[0];
|
|
1077
|
-
}
|
|
1078
|
-
function applyStoryMetrics(plan, storyIds, share, durationSeconds) {
|
|
1079
|
-
if (storyIds.length === 0) return plan;
|
|
1080
|
-
const targets = new Set(storyIds);
|
|
1081
|
-
return {
|
|
1082
|
-
...plan,
|
|
1083
|
-
userStories: plan.userStories.map((story) => {
|
|
1084
|
-
if (!targets.has(story.id)) return story;
|
|
1085
|
-
const updated = { ...story, ...sumUsage(story, share) };
|
|
1086
|
-
if (story.durationSeconds !== void 0 || durationSeconds !== void 0) {
|
|
1087
|
-
updated.durationSeconds = (story.durationSeconds ?? 0) + (durationSeconds ?? 0);
|
|
1088
|
-
}
|
|
1089
|
-
return updated;
|
|
1090
|
-
})
|
|
1091
|
-
};
|
|
1092
|
-
}
|
|
1093
|
-
function markIssueInProgress(plan, timestamp) {
|
|
1094
|
-
const ts = timestamp ?? isoNow();
|
|
1095
|
-
return {
|
|
1096
|
-
...plan,
|
|
1097
|
-
issueStatus: "in_progress",
|
|
1098
|
-
completedAt: null,
|
|
1099
|
-
lastAttemptAt: ts
|
|
1100
|
-
};
|
|
1101
|
-
}
|
|
1102
|
-
function markIssueCompleted(plan) {
|
|
1103
|
-
const ts = isoNow();
|
|
1104
|
-
return {
|
|
1105
|
-
...plan,
|
|
1106
|
-
issueStatus: "completed",
|
|
1107
|
-
completedAt: ts,
|
|
1108
|
-
lastAttemptAt: ts,
|
|
1109
|
-
lastError: null,
|
|
1110
|
-
pipeline: plan.pipeline ? { ...plan.pipeline, executionCompleted: true } : plan.pipeline
|
|
1111
|
-
};
|
|
1112
|
-
}
|
|
1113
|
-
function setLastError(plan, category, message) {
|
|
1114
|
-
const ts = isoNow();
|
|
1115
|
-
const error = {
|
|
1116
|
-
category,
|
|
1117
|
-
message,
|
|
1118
|
-
at: ts
|
|
1119
|
-
};
|
|
1120
|
-
return {
|
|
1121
|
-
...plan,
|
|
1122
|
-
lastAttemptAt: ts,
|
|
1123
|
-
lastError: error
|
|
1124
|
-
};
|
|
1125
|
-
}
|
|
1126
|
-
function clearLastError(plan, attemptStartedAt) {
|
|
1127
|
-
const ts = isoNow();
|
|
1128
|
-
if (plan.lastError && plan.lastError.at > attemptStartedAt) {
|
|
1129
|
-
return {
|
|
1130
|
-
...plan,
|
|
1131
|
-
lastAttemptAt: ts
|
|
1132
|
-
};
|
|
1133
|
-
}
|
|
1134
|
-
return {
|
|
1135
|
-
...plan,
|
|
1136
|
-
lastAttemptAt: ts,
|
|
1137
|
-
lastError: null
|
|
1138
|
-
};
|
|
1139
|
-
}
|
|
1140
|
-
function trimErrorMessage(message) {
|
|
1141
|
-
return message.split("\n").filter((line) => line.trim().length > 0).slice(0, 8).join("\n");
|
|
1142
|
-
}
|
|
1143
|
-
|
|
1144
595
|
// src/core/verbose.ts
|
|
1145
596
|
var _verbose = false;
|
|
1146
597
|
function setVerbose(enabled) {
|
|
@@ -1177,6 +628,13 @@ function setGlobalTimeout(ms) {
|
|
|
1177
628
|
function getGlobalTimeout() {
|
|
1178
629
|
return _globalTimeout;
|
|
1179
630
|
}
|
|
631
|
+
var _inactivityTimeout;
|
|
632
|
+
function setInactivityTimeout(ms) {
|
|
633
|
+
_inactivityTimeout = ms;
|
|
634
|
+
}
|
|
635
|
+
function getInactivityTimeout() {
|
|
636
|
+
return _inactivityTimeout;
|
|
637
|
+
}
|
|
1180
638
|
|
|
1181
639
|
// src/ui/logger.ts
|
|
1182
640
|
import chalk from "chalk";
|
|
@@ -1325,38 +783,11 @@ var ElapsedTimer = class {
|
|
|
1325
783
|
};
|
|
1326
784
|
|
|
1327
785
|
export {
|
|
1328
|
-
policyConfigSchema,
|
|
1329
|
-
policyConfigInputSchema,
|
|
1330
|
-
pullRequestRefSchema,
|
|
1331
|
-
issueMetadataSchema,
|
|
1332
|
-
taskPlanSchema,
|
|
1333
|
-
sessionSnapshotSchema,
|
|
1334
|
-
webConfigSchema,
|
|
1335
|
-
issuesConfigSchema,
|
|
1336
|
-
prReviewConfigSchema,
|
|
1337
786
|
NullPublisher,
|
|
787
|
+
MemoryPublisher,
|
|
1338
788
|
FilePublisher,
|
|
1339
789
|
setSessionPublisher,
|
|
1340
790
|
getSessionPublisher,
|
|
1341
|
-
writeFileAtomic,
|
|
1342
|
-
parseUsage,
|
|
1343
|
-
sumUsage,
|
|
1344
|
-
divideUsage,
|
|
1345
|
-
formatTokens,
|
|
1346
|
-
hasUsageData,
|
|
1347
|
-
isoNow,
|
|
1348
|
-
loadTaskPlan,
|
|
1349
|
-
saveTaskPlan,
|
|
1350
|
-
initializeState,
|
|
1351
|
-
hasPendingCorrection,
|
|
1352
|
-
allStoriesPass,
|
|
1353
|
-
selectActiveStory,
|
|
1354
|
-
applyStoryMetrics,
|
|
1355
|
-
markIssueInProgress,
|
|
1356
|
-
markIssueCompleted,
|
|
1357
|
-
setLastError,
|
|
1358
|
-
clearLastError,
|
|
1359
|
-
trimErrorMessage,
|
|
1360
791
|
setVerbose,
|
|
1361
792
|
isVerbose,
|
|
1362
793
|
setOutputCallback,
|
|
@@ -1367,6 +798,8 @@ export {
|
|
|
1367
798
|
getStoryStageCallback,
|
|
1368
799
|
setGlobalTimeout,
|
|
1369
800
|
getGlobalTimeout,
|
|
801
|
+
setInactivityTimeout,
|
|
802
|
+
getInactivityTimeout,
|
|
1370
803
|
useUnicode,
|
|
1371
804
|
useColor,
|
|
1372
805
|
getIcons,
|
|
@@ -1380,4 +813,4 @@ export {
|
|
|
1380
813
|
formatDuration,
|
|
1381
814
|
ElapsedTimer
|
|
1382
815
|
};
|
|
1383
|
-
//# sourceMappingURL=chunk-
|
|
816
|
+
//# sourceMappingURL=chunk-BYZDPR7N.js.map
|