issue-flow 0.12.0 → 0.14.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/README.md +79 -274
- package/dist/agent-OX3KPHE4.js +30 -0
- package/dist/{analyze-OEET7KGE.js → analyze-WIND2SEB.js} +31 -16
- package/dist/analyze-WIND2SEB.js.map +1 -0
- package/dist/{apply-ZIA3C52S.js → apply-JNOI2LR4.js} +18 -7
- package/dist/{apply-ZIA3C52S.js.map → apply-JNOI2LR4.js.map} +1 -1
- package/dist/bench-B4VZON7E.js +910 -0
- package/dist/bench-B4VZON7E.js.map +1 -0
- package/dist/{chunk-FADTCQGM.js → chunk-2IQ6M4ZE.js} +94 -32
- package/dist/chunk-2IQ6M4ZE.js.map +1 -0
- package/dist/chunk-3BWZO4YI.js +141 -0
- package/dist/chunk-3BWZO4YI.js.map +1 -0
- package/dist/chunk-3WFZETIM.js +163 -0
- package/dist/chunk-3WFZETIM.js.map +1 -0
- package/dist/chunk-67YIU2VB.js +362 -0
- package/dist/chunk-67YIU2VB.js.map +1 -0
- package/dist/{chunk-QCPGHBSZ.js → chunk-6BENHR2I.js} +63 -21
- package/dist/chunk-6BENHR2I.js.map +1 -0
- package/dist/{chunk-BYZDPR7N.js → chunk-6QYMSNVA.js} +194 -235
- package/dist/chunk-6QYMSNVA.js.map +1 -0
- package/dist/{chunk-Y5ZDJ4PJ.js → chunk-76MMPJ53.js} +20 -10
- package/dist/chunk-76MMPJ53.js.map +1 -0
- package/dist/chunk-7Z4Y3VKM.js +26 -0
- package/dist/chunk-7Z4Y3VKM.js.map +1 -0
- package/dist/{chunk-3QHK4YQA.js → chunk-B72RQZBV.js} +346 -119
- package/dist/chunk-B72RQZBV.js.map +1 -0
- package/dist/chunk-BEZ5BA5U.js +625 -0
- package/dist/chunk-BEZ5BA5U.js.map +1 -0
- package/dist/chunk-BRCNQXYQ.js +74 -0
- package/dist/chunk-BRCNQXYQ.js.map +1 -0
- package/dist/chunk-CGKLE6WR.js +1203 -0
- package/dist/chunk-CGKLE6WR.js.map +1 -0
- package/dist/chunk-CRGIQFUQ.js +83 -0
- package/dist/chunk-CRGIQFUQ.js.map +1 -0
- package/dist/{chunk-NS6R37II.js → chunk-F6TOYSYY.js} +141 -12
- package/dist/chunk-F6TOYSYY.js.map +1 -0
- package/dist/chunk-G5EK2XRN.js +25 -0
- package/dist/chunk-G5EK2XRN.js.map +1 -0
- package/dist/chunk-HAKDLR6N.js +134 -0
- package/dist/chunk-HAKDLR6N.js.map +1 -0
- package/dist/chunk-J3SOTUVI.js +298 -0
- package/dist/chunk-J3SOTUVI.js.map +1 -0
- package/dist/chunk-JN6D3MY6.js +30 -0
- package/dist/chunk-JN6D3MY6.js.map +1 -0
- package/dist/{chunk-LAFNLLQP.js → chunk-KMRFRXIT.js} +5 -5
- package/dist/chunk-KT7JKOXB.js +72 -0
- package/dist/chunk-KT7JKOXB.js.map +1 -0
- package/dist/chunk-L6HSKTQE.js +77 -0
- package/dist/chunk-L6HSKTQE.js.map +1 -0
- package/dist/chunk-LBGHI4S2.js +1264 -0
- package/dist/chunk-LBGHI4S2.js.map +1 -0
- package/dist/chunk-LEW4QM74.js +180 -0
- package/dist/chunk-LEW4QM74.js.map +1 -0
- package/dist/{chunk-FJP3UQGD.js → chunk-LFEQIZLB.js} +115 -20
- package/dist/chunk-LFEQIZLB.js.map +1 -0
- package/dist/chunk-OOVG2KBY.js +77 -0
- package/dist/chunk-OOVG2KBY.js.map +1 -0
- package/dist/{chunk-56KS3TFQ.js → chunk-OR43364P.js} +115 -160
- package/dist/chunk-OR43364P.js.map +1 -0
- package/dist/chunk-P3LRUNC6.js +16 -0
- package/dist/chunk-P3LRUNC6.js.map +1 -0
- package/dist/{chunk-TSOLWK7Y.js → chunk-PBTA3QQX.js} +8 -7
- package/dist/{chunk-TSOLWK7Y.js.map → chunk-PBTA3QQX.js.map} +1 -1
- package/dist/{chunk-MJULIKQU.js → chunk-PHW2YV6N.js} +23 -47
- package/dist/chunk-PHW2YV6N.js.map +1 -0
- package/dist/{chunk-73ATPIYX.js → chunk-PZTWU277.js} +36 -71
- package/dist/chunk-PZTWU277.js.map +1 -0
- package/dist/{chunk-M5UCXTIT.js → chunk-QD4VOILZ.js} +22 -18
- package/dist/chunk-QD4VOILZ.js.map +1 -0
- package/dist/chunk-QJ5UZAH5.js +2718 -0
- package/dist/chunk-QJ5UZAH5.js.map +1 -0
- package/dist/chunk-QK7WUO7A.js +283 -0
- package/dist/chunk-QK7WUO7A.js.map +1 -0
- package/dist/{chunk-A4SLHOW7.js → chunk-QZVKRDJY.js} +27 -19
- package/dist/chunk-QZVKRDJY.js.map +1 -0
- package/dist/chunk-REO3PXXO.js +71 -0
- package/dist/chunk-REO3PXXO.js.map +1 -0
- package/dist/{chunk-EZATO3DW.js → chunk-RVO7L4FX.js} +594 -17
- package/dist/chunk-RVO7L4FX.js.map +1 -0
- package/dist/{chunk-XQUSIEKA.js → chunk-SZMHCEYV.js} +63 -14
- package/dist/chunk-SZMHCEYV.js.map +1 -0
- package/dist/chunk-TYRNTSPJ.js +243 -0
- package/dist/chunk-TYRNTSPJ.js.map +1 -0
- package/dist/chunk-UJ3L2MS7.js +238 -0
- package/dist/chunk-UJ3L2MS7.js.map +1 -0
- package/dist/chunk-V7MO5EDS.js +120 -0
- package/dist/chunk-V7MO5EDS.js.map +1 -0
- package/dist/chunk-WB2X37MO.js +415 -0
- package/dist/chunk-WB2X37MO.js.map +1 -0
- package/dist/{chunk-NTMMF5UD.js → chunk-X44FKNKP.js} +525 -252
- package/dist/chunk-X44FKNKP.js.map +1 -0
- package/dist/chunk-XEYH6ZZI.js +289 -0
- package/dist/chunk-XEYH6ZZI.js.map +1 -0
- package/dist/cli.js +267 -44
- package/dist/cli.js.map +1 -1
- package/dist/{config-PRJGWW7J.js → config-HMFMCY2M.js} +31 -5
- package/dist/contract-JH2WBIO6.js +8 -0
- package/dist/conventions-NJF2OPVQ.js +146 -0
- package/dist/conventions-NJF2OPVQ.js.map +1 -0
- package/dist/diagnostics-THH42ZMP.js +23 -0
- package/dist/execute-KFNNZZHI.js +35 -0
- package/dist/{generate-EGB4KV2B.js → generate-FW5C6NP7.js} +37 -20
- package/dist/{generate-EGB4KV2B.js.map → generate-FW5C6NP7.js.map} +1 -1
- package/dist/git-W72QQ6GT.js +33 -0
- package/dist/init-HTN4V67Q.js +24 -0
- package/dist/{operations-BYT626KQ.js → operations-BCFOBH42.js} +27 -15
- package/dist/{operations-BYT626KQ.js.map → operations-BCFOBH42.js.map} +1 -1
- package/dist/permissions-4AKNQZHK.js +18 -0
- package/dist/plan-SIZLGA4V.js +34 -0
- package/dist/{policy-6S4GFC76.js → policy-DT6Q6ZVM.js} +20 -6
- package/dist/policy-DT6Q6ZVM.js.map +1 -0
- package/dist/pr-OMWCGUOW.js +39 -0
- package/dist/pr-review-N24CZPKR.js +32 -0
- package/dist/pr-review-N24CZPKR.js.map +1 -0
- package/dist/prd-5OFTZAHJ.js +33 -0
- package/dist/prd-5OFTZAHJ.js.map +1 -0
- package/dist/ps-RFORTCUD.js +93 -0
- package/dist/ps-RFORTCUD.js.map +1 -0
- package/dist/recorder-KCCPMKQG.js +32 -0
- package/dist/recorder-KCCPMKQG.js.map +1 -0
- package/dist/registry-GLPXOMFG.js +15 -0
- package/dist/registry-GLPXOMFG.js.map +1 -0
- package/dist/{resume-PPCNQVVV.js → resume-M5RLKP4I.js} +55 -34
- package/dist/{resume-PPCNQVVV.js.map → resume-M5RLKP4I.js.map} +1 -1
- package/dist/review-YM4JAABY.js +35 -0
- package/dist/review-YM4JAABY.js.map +1 -0
- package/dist/routing-NNT5WKGX.js +94 -0
- package/dist/routing-NNT5WKGX.js.map +1 -0
- package/dist/run-PZUKHPQK.js +54 -0
- package/dist/run-PZUKHPQK.js.map +1 -0
- package/dist/session-publisher-T7EI3CIP.js +11 -0
- package/dist/session-publisher-T7EI3CIP.js.map +1 -0
- package/dist/{state-manager-2EDVDBAR.js → state-manager-GQH32C44.js} +4 -2
- package/dist/state-manager-GQH32C44.js.map +1 -0
- package/dist/usage-V2CHE5PY.js +177 -0
- package/dist/usage-V2CHE5PY.js.map +1 -0
- package/dist/{web-EF5WIIGG.js → web-EA3KGNFN.js} +45 -30
- package/dist/web-EA3KGNFN.js.map +1 -0
- package/package.json +6 -2
- package/prompts/execute.md +7 -8
- package/prompts/plan.md +4 -5
- package/prompts/pr.md +2 -2
- package/web/AGENTS.md +152 -13
- package/web/public/app.css +380 -109
- package/web/public/app.js +525 -25
- package/web/public/index.html +68 -1
- package/dist/analyze-OEET7KGE.js.map +0 -1
- package/dist/chunk-3QHK4YQA.js.map +0 -1
- package/dist/chunk-42PJFMAR.js +0 -605
- package/dist/chunk-42PJFMAR.js.map +0 -1
- package/dist/chunk-4JSOYE4G.js +0 -368
- package/dist/chunk-4JSOYE4G.js.map +0 -1
- package/dist/chunk-56KS3TFQ.js.map +0 -1
- package/dist/chunk-73ATPIYX.js.map +0 -1
- package/dist/chunk-A4SLHOW7.js.map +0 -1
- package/dist/chunk-BAUPQXPE.js +0 -173
- package/dist/chunk-BAUPQXPE.js.map +0 -1
- package/dist/chunk-BYZDPR7N.js.map +0 -1
- package/dist/chunk-CD5ULRVC.js +0 -615
- package/dist/chunk-CD5ULRVC.js.map +0 -1
- package/dist/chunk-DIDUVL6O.js +0 -1365
- package/dist/chunk-DIDUVL6O.js.map +0 -1
- package/dist/chunk-EZATO3DW.js.map +0 -1
- package/dist/chunk-FADTCQGM.js.map +0 -1
- package/dist/chunk-FJP3UQGD.js.map +0 -1
- package/dist/chunk-LWRSMTTZ.js +0 -18
- package/dist/chunk-LWRSMTTZ.js.map +0 -1
- package/dist/chunk-M5UCXTIT.js.map +0 -1
- package/dist/chunk-M66SJHQQ.js +0 -206
- package/dist/chunk-M66SJHQQ.js.map +0 -1
- package/dist/chunk-MJULIKQU.js.map +0 -1
- package/dist/chunk-NS6R37II.js.map +0 -1
- package/dist/chunk-NTMMF5UD.js.map +0 -1
- package/dist/chunk-QCPGHBSZ.js.map +0 -1
- package/dist/chunk-XQUSIEKA.js.map +0 -1
- package/dist/chunk-Y5ZDJ4PJ.js.map +0 -1
- package/dist/execute-XBNVL753.js +0 -18
- package/dist/init-AJHCXJBP.js +0 -10
- package/dist/plan-QOZ3RIO6.js +0 -21
- package/dist/policy-6S4GFC76.js.map +0 -1
- package/dist/pr-EBBHD2ZX.js +0 -26
- package/dist/pr-review-VVMW5PO4.js +0 -20
- package/dist/prd-JQSZF7PR.js +0 -21
- package/dist/review-3F6MARWH.js +0 -19
- package/dist/run-RXE5GDQO.js +0 -36
- package/dist/web-EF5WIIGG.js.map +0 -1
- /package/dist/{config-PRJGWW7J.js.map → agent-OX3KPHE4.js.map} +0 -0
- /package/dist/{chunk-LAFNLLQP.js.map → chunk-KMRFRXIT.js.map} +0 -0
- /package/dist/{execute-XBNVL753.js.map → config-HMFMCY2M.js.map} +0 -0
- /package/dist/{init-AJHCXJBP.js.map → contract-JH2WBIO6.js.map} +0 -0
- /package/dist/{plan-QOZ3RIO6.js.map → diagnostics-THH42ZMP.js.map} +0 -0
- /package/dist/{pr-EBBHD2ZX.js.map → execute-KFNNZZHI.js.map} +0 -0
- /package/dist/{pr-review-VVMW5PO4.js.map → git-W72QQ6GT.js.map} +0 -0
- /package/dist/{prd-JQSZF7PR.js.map → init-HTN4V67Q.js.map} +0 -0
- /package/dist/{review-3F6MARWH.js.map → permissions-4AKNQZHK.js.map} +0 -0
- /package/dist/{run-RXE5GDQO.js.map → plan-SIZLGA4V.js.map} +0 -0
- /package/dist/{state-manager-2EDVDBAR.js.map → pr-OMWCGUOW.js.map} +0 -0
|
@@ -1,45 +1,60 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import "./chunk-3D4A4B46.js";
|
|
3
2
|
import {
|
|
4
|
-
ensureProvidersRegistered,
|
|
5
|
-
getProvider,
|
|
6
3
|
localIssueRef
|
|
7
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-REO3PXXO.js";
|
|
8
5
|
import {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
} from "./chunk-
|
|
6
|
+
ensureProvidersRegistered,
|
|
7
|
+
getProvider
|
|
8
|
+
} from "./chunk-PZTWU277.js";
|
|
12
9
|
import "./chunk-KMMASOA6.js";
|
|
13
10
|
import {
|
|
14
11
|
publishPhaseMetrics,
|
|
15
12
|
resolvePolicyPlaceholders
|
|
16
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-PHW2YV6N.js";
|
|
17
14
|
import {
|
|
18
15
|
applyPlaceholders,
|
|
19
16
|
loadPrompt
|
|
20
|
-
} from "./chunk-
|
|
21
|
-
import
|
|
17
|
+
} from "./chunk-KMRFRXIT.js";
|
|
18
|
+
import {
|
|
19
|
+
DEFAULT_HEADLESS_TIMEOUT_MS,
|
|
20
|
+
runHeadless
|
|
21
|
+
} from "./chunk-CGKLE6WR.js";
|
|
22
|
+
import "./chunk-LEW4QM74.js";
|
|
23
|
+
import "./chunk-3BWZO4YI.js";
|
|
24
|
+
import "./chunk-OOVG2KBY.js";
|
|
25
|
+
import "./chunk-3D4A4B46.js";
|
|
22
26
|
import {
|
|
23
27
|
loadRepositoryPolicy
|
|
24
|
-
} from "./chunk-
|
|
28
|
+
} from "./chunk-RVO7L4FX.js";
|
|
25
29
|
import {
|
|
26
30
|
loadIssuesConfig,
|
|
27
31
|
loadPolicyConfig
|
|
28
|
-
} from "./chunk-
|
|
32
|
+
} from "./chunk-QJ5UZAH5.js";
|
|
29
33
|
import {
|
|
30
|
-
resolveProjectPaths
|
|
31
|
-
|
|
32
|
-
} from "./chunk-DIDUVL6O.js";
|
|
34
|
+
resolveProjectPaths
|
|
35
|
+
} from "./chunk-QK7WUO7A.js";
|
|
33
36
|
import {
|
|
34
37
|
getGlobalTimeout,
|
|
35
38
|
printError,
|
|
36
39
|
printInfo,
|
|
37
40
|
printSuccess,
|
|
38
41
|
printWarning
|
|
39
|
-
} from "./chunk-
|
|
42
|
+
} from "./chunk-TYRNTSPJ.js";
|
|
43
|
+
import "./chunk-6QYMSNVA.js";
|
|
44
|
+
import "./chunk-3WFZETIM.js";
|
|
45
|
+
import "./chunk-HAKDLR6N.js";
|
|
46
|
+
import {
|
|
47
|
+
run
|
|
48
|
+
} from "./chunk-BEZ5BA5U.js";
|
|
49
|
+
import "./chunk-67YIU2VB.js";
|
|
50
|
+
import "./chunk-P3LRUNC6.js";
|
|
51
|
+
import "./chunk-G5EK2XRN.js";
|
|
40
52
|
import {
|
|
41
53
|
isoNow
|
|
42
|
-
} from "./chunk-
|
|
54
|
+
} from "./chunk-J3SOTUVI.js";
|
|
55
|
+
import "./chunk-LBGHI4S2.js";
|
|
56
|
+
import "./chunk-7Z4Y3VKM.js";
|
|
57
|
+
import "./chunk-XEYH6ZZI.js";
|
|
43
58
|
|
|
44
59
|
// src/issues/draft.ts
|
|
45
60
|
var DRAFT_TAG = "issue-draft";
|
|
@@ -149,9 +164,11 @@ async function draftIssue(promptText) {
|
|
|
149
164
|
outputFormat: "json",
|
|
150
165
|
allowedTools: ["Bash", "Read", "Glob", "Grep"],
|
|
151
166
|
addDirs: [issuesDir],
|
|
152
|
-
statusMessage: "Drafting issue..."
|
|
167
|
+
statusMessage: "Drafting issue...",
|
|
168
|
+
phase: "generate",
|
|
169
|
+
permission: "workspace"
|
|
153
170
|
});
|
|
154
|
-
publishPhaseMetrics("generate", result.cost, startedAtMs);
|
|
171
|
+
publishPhaseMetrics("generate", result.cost, startedAtMs, result.agent?.provider);
|
|
155
172
|
if (!result.success) {
|
|
156
173
|
throw new Error(`Issue creation failed: ${result.error}`);
|
|
157
174
|
}
|
|
@@ -247,4 +264,4 @@ async function runGenerate(promptText, target) {
|
|
|
247
264
|
export {
|
|
248
265
|
runGenerate
|
|
249
266
|
};
|
|
250
|
-
//# sourceMappingURL=generate-
|
|
267
|
+
//# sourceMappingURL=generate-FW5C6NP7.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/issues/draft.ts","../src/issues/label-policy.ts","../src/commands/generate.ts"],"sourcesContent":["import type { IssueDraft } from './types.js';\n\n/**\n * Structured Issue draft emitted by the `generate` prompt.\n *\n * The agent no longer creates the Issue itself: it returns the content and the\n * providers persist it, so the same draft can reach GitHub, the local files, or\n * both. Capturing the result therefore has to be exact — the old approach of\n * fishing an issue URL out of free-form prose only ever worked for GitHub.\n *\n * The block mirrors the `<issue-body>` convention already used by the phase\n * templates, which keeps a multi-line markdown body readable and free of the\n * escaping a JSON payload would demand.\n */\n\n/** Wrapper the agent must emit around the draft. */\nexport const DRAFT_TAG = 'issue-draft';\n\n/** Failure to capture a draft from the agent output. */\nexport class IssueDraftParseError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'IssueDraftParseError';\n }\n}\n\n/**\n * Last occurrence of `<tag>...</tag>` in `source`, or `null`.\n *\n * The last one wins: an agent that shows the format before filling it in (or\n * restates the draft after a correction) would otherwise have its first,\n * throwaway attempt captured. The closing tag is searched from the end for the\n * same reason a body may legitimately quote one.\n */\nfunction extractTag(source: string, tag: string): string | null {\n const open = `<${tag}>`;\n const close = `</${tag}>`;\n\n const start = source.lastIndexOf(open);\n if (start === -1) return null;\n\n const end = source.lastIndexOf(close);\n if (end === -1 || end < start) return null;\n\n return source.slice(start + open.length, end);\n}\n\nfunction parseLabels(raw: string | null): string[] {\n if (raw === null) return [];\n return raw\n .split(',')\n .map((label) => label.trim())\n .filter((label) => label.length > 0 && label.toLowerCase() !== '(none)');\n}\n\n/**\n * Read the draft the agent produced.\n *\n * @throws IssueDraftParseError when the block is absent or has no usable\n * title/body, so a malformed answer fails loudly instead of creating an empty\n * Issue.\n */\nexport function parseIssueDraft(output: string): IssueDraft {\n const normalized = output.replace(/\\r\\n?/g, '\\n');\n const block = extractTag(normalized, DRAFT_TAG);\n\n if (block === null) {\n throw new IssueDraftParseError(\n `The agent did not emit an <${DRAFT_TAG}> block, so there is no Issue to create. ` +\n `Output was: ${normalized.trim().slice(0, 300) || '(empty)'}`,\n );\n }\n\n const title = (extractTag(block, 'title') ?? '').trim();\n const body = (extractTag(block, 'body') ?? '').trim();\n\n if (title.length === 0) {\n throw new IssueDraftParseError(`The <${DRAFT_TAG}> block has no <title>.`);\n }\n if (body.length === 0) {\n throw new IssueDraftParseError(`The <${DRAFT_TAG}> block has no <body>.`);\n }\n\n // Optional, and deliberately so: a repository with no Issue Types and no\n // templates emits neither, and the draft is exactly what it was before.\n const type = (extractTag(block, 'type') ?? '').trim();\n const template = (extractTag(block, 'template') ?? '').trim();\n\n return {\n title,\n body,\n labels: parseLabels(extractTag(block, 'labels')),\n ...(type === '' || type.toLowerCase() === '(none)' ? {} : { type }),\n ...(template === '' || template.toLowerCase() === '(none)' ? {} : { template }),\n };\n}\n","import type { LabelDefinition } from '../policy/types.js';\nimport { run } from '../utils/shell.js';\n\n/**\n * Reconciling the labels an agent suggested with the ones the repository\n * actually has.\n *\n * A workflow tool has no business changing a repository's taxonomy. Labels are\n * governance: a team that deleted `high`/`medium`/`low` in favour of a native\n * priority field, or `bug`/`enhancement` in favour of Issue Types, made a\n * decision — and a generator that silently recreates them undoes it, quietly and\n * repository-wide.\n *\n * So the default is to **validate, never create**. `policy.issues.allowLabelCreation`\n * restores the old behaviour for repositories that want it.\n */\n\nexport interface LabelReconciliation {\n /** Labels to apply, in the repository's own casing. */\n labels: string[];\n /** Suggested labels the repository does not have. */\n missing: string[];\n}\n\n/**\n * Intersect the suggested labels with the repository's real ones.\n *\n * Matching is case-insensitive and the result carries the **repository's**\n * casing: `gh issue create --label Bug` fails on a repository whose label is\n * `bug`, and an agent has no way to know which one it is.\n *\n * An empty `known` means the labels could not be read at all — no policy, no\n * `gh`, no network. That is not evidence the labels do not exist, so the\n * suggestions pass through untouched and the behaviour is the one that shipped\n * before this layer. Refusing to label an issue because discovery was offline\n * would be the wrong failure.\n */\nexport function reconcileLabels(\n suggested: string[],\n known: LabelDefinition[],\n): LabelReconciliation {\n if (known.length === 0) {\n return { labels: [...suggested], missing: [] };\n }\n\n const canonical = new Map(known.map((label) => [label.name.toLowerCase(), label.name]));\n\n const labels: string[] = [];\n const missing: string[] = [];\n for (const suggestion of suggested) {\n const match = canonical.get(suggestion.trim().toLowerCase());\n if (match === undefined) {\n if (!missing.includes(suggestion)) missing.push(suggestion);\n continue;\n }\n if (!labels.includes(match)) labels.push(match);\n }\n\n return { labels, missing };\n}\n\n/**\n * Create the labels the repository is missing, for\n * `policy.issues.allowLabelCreation: true`.\n *\n * Best-effort per label: one that cannot be created is reported and skipped, so\n * a single failure does not cost the Issue. Returns the labels that now exist.\n */\nexport async function createMissingLabels(\n names: string[],\n warn: (message: string) => void,\n): Promise<string[]> {\n const created: string[] = [];\n\n for (const name of names) {\n const result = await run('gh', ['label', 'create', name]);\n if (result.exitCode === 0) {\n created.push(name);\n continue;\n }\n // gh answers \"already exists\" when a label was created between the\n // discovery and now, which is a success for our purposes.\n const detail = (result.stderr || result.stdout).trim();\n if (/already exists/i.test(detail)) {\n created.push(name);\n continue;\n }\n warn(`Could not create label \"${name}\": ${detail || 'gh label create failed'}`);\n }\n\n return created;\n}\n","import { loadIssuesConfig, loadPolicyConfig } from '../config.js';\nimport { DEFAULT_HEADLESS_TIMEOUT_MS, runHeadless } from '../core/headless.js';\nimport { applyPlaceholders, loadPrompt } from '../core/prompt-resolver.js';\nimport { publishPhaseMetrics } from '../core/session-metrics.js';\nimport { isoNow } from '../core/state-manager.js';\nimport { getGlobalTimeout } from '../core/verbose.js';\nimport { ensureProvidersRegistered } from '../issues/bootstrap.js';\nimport { localIssueRef } from '../issues/context.js';\nimport { IssueDraftParseError, parseIssueDraft } from '../issues/draft.js';\nimport { createMissingLabels, reconcileLabels } from '../issues/label-policy.js';\nimport { getProvider } from '../issues/registry.js';\nimport type { Issue, IssueDraft, IssueGenerateTarget } from '../issues/types.js';\nimport { loadRepositoryPolicy } from '../policy/index.js';\nimport { resolvePolicyPlaceholders } from '../policy/placeholders.js';\nimport { resolveProjectPaths } from '../storage/resolve.js';\nimport { printError, printInfo, printSuccess, printWarning } from '../ui/logger.js';\n\n/** Human-readable pointer to a created Issue. */\nfunction issueLocation(issue: Issue): string {\n return issue.remoteRef ?? localIssueRef(issue.id);\n}\n\n/**\n * Ask the agent for the Issue content.\n *\n * The agent only drafts: creation belongs to the providers, so the same draft\n * can be persisted to GitHub, to the local files, or to both.\n */\nasync function draftIssue(promptText: string): Promise<IssueDraft> {\n // The duplicate check reads the local issues, which live in the global\n // storage: the path has to be handed over (and allowed) explicitly, since it\n // is outside the working directory the agent is started in.\n const { issuesDir } = await resolveProjectPaths();\n\n const template = await loadPrompt('generate');\n const prompt = applyPlaceholders(template, {\n // The repository's own conventions. Empty when it declares none, which is\n // what keeps the rendered prompt identical to the pre-policy one.\n ...(await resolvePolicyPlaceholders()),\n __USER_PROMPT__: promptText,\n __LOCAL_ISSUES_DIR__: issuesDir,\n });\n\n const startedAtMs = Date.now();\n const result = await runHeadless({\n prompt,\n maxTurns: 15,\n timeout: getGlobalTimeout() ?? DEFAULT_HEADLESS_TIMEOUT_MS,\n // json (not text) so the CLI reports usage: the envelope's `result` field\n // carries the same assistant text parseIssueDraft() already consumed.\n outputFormat: 'json',\n allowedTools: ['Bash', 'Read', 'Glob', 'Grep'],\n addDirs: [issuesDir],\n statusMessage: 'Drafting issue...',\n });\n // Before the success check: the tokens were spent either way.\n publishPhaseMetrics('generate', result.cost, startedAtMs);\n\n if (!result.success) {\n throw new Error(`Issue creation failed: ${result.error}`);\n }\n\n return parseIssueDraft(result.result);\n}\n\n/**\n * Create the GitHub Issue and mirror it locally.\n *\n * The remote comes first on purpose: it owns the identifier, and the mirror has\n * to reuse it so both copies describe one demand. A remote failure therefore\n * leaves nothing behind, and a mirror failure is reported with the remote\n * reference that already exists.\n */\nasync function createBoth(draft: IssueDraft): Promise<Issue[]> {\n const remote = await getProvider('github').create(draft);\n\n try {\n const mirror = await getProvider('local').create({\n ...draft,\n id: remote.id,\n ...(remote.remoteRef\n ? {\n remote: {\n provider: 'github' as const,\n ref: remote.remoteRef,\n syncedAt: isoNow(),\n syncedContentHash: remote.contentHash,\n },\n }\n : {}),\n });\n return [remote, mirror];\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n throw new Error(\n `GitHub issue created at ${issueLocation(remote)}, but the local mirror was not written: ` +\n `${message}. Nothing else was persisted; re-run with --local to create the mirror.`,\n );\n }\n}\n\nasync function createIssues(target: IssueGenerateTarget, draft: IssueDraft): Promise<Issue[]> {\n if (target === 'both') {\n return createBoth(draft);\n }\n return [await getProvider(target).create(draft)];\n}\n\n/**\n * Reconcile the draft's labels with the ones the repository really has.\n *\n * Two failures are being prevented here, and only one of them is cosmetic.\n * GitHub rejects an issue whose label does not exist, so an invented label costs\n * the whole Issue. And a tool that creates the label instead rewrites a taxonomy\n * the team may have curated deliberately — which is worse, because it succeeds.\n *\n * Never throws: a policy that could not be resolved leaves the draft untouched.\n */\nasync function applyLabelPolicy(draft: IssueDraft): Promise<IssueDraft> {\n let known: Awaited<ReturnType<typeof loadRepositoryPolicy>>['issues']['labels'];\n let allowCreation: boolean;\n try {\n const policy = await loadRepositoryPolicy();\n known = policy.issues.labels;\n const config = await loadPolicyConfig({ projectRoot: policy.root });\n allowCreation = config.issues.allowLabelCreation ?? false;\n } catch {\n return draft;\n }\n\n const { labels, missing } = reconcileLabels(draft.labels, known);\n if (missing.length === 0) {\n return { ...draft, labels };\n }\n\n if (!allowCreation) {\n printWarning(\n `Dropping ${missing.length} label(s) this repository does not have: ${missing.join(', ')}. ` +\n 'Issue Flow does not create labels; set policy.issues.allowLabelCreation to change that.',\n );\n return { ...draft, labels };\n }\n\n const created = await createMissingLabels(missing, printWarning);\n return { ...draft, labels: [...labels, ...created] };\n}\n\nexport async function runGenerate(\n promptText: string,\n target?: IssueGenerateTarget,\n): Promise<number> {\n const config = await loadIssuesConfig();\n const destination = target ?? config.defaultGenerateTarget;\n\n ensureProvidersRegistered();\n\n let draft: IssueDraft;\n try {\n draft = await draftIssue(promptText);\n } catch (err) {\n if (err instanceof IssueDraftParseError) {\n printError(`Could not read the generated Issue draft: ${err.message}`);\n } else {\n printError(err instanceof Error ? err.message : String(err));\n }\n return 1;\n }\n\n const finalDraft = await applyLabelPolicy(draft);\n if (finalDraft.template !== undefined) {\n printInfo(`Following the repository's Issue Template: ${finalDraft.template}`);\n }\n if (finalDraft.type !== undefined) {\n printInfo(`Issue Type: ${finalDraft.type}`);\n }\n\n let created: Issue[];\n try {\n created = await createIssues(destination, finalDraft);\n } catch (err) {\n printError(err instanceof Error ? err.message : String(err));\n return 1;\n }\n\n for (const issue of created) {\n printSuccess(`Issue created (${issue.source}): ${issueLocation(issue)}`);\n }\n return 0;\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/issues/draft.ts","../src/issues/label-policy.ts","../src/commands/generate.ts"],"sourcesContent":["import type { IssueDraft } from './types.js';\n\n/**\n * Structured Issue draft emitted by the `generate` prompt.\n *\n * The agent no longer creates the Issue itself: it returns the content and the\n * providers persist it, so the same draft can reach GitHub, the local files, or\n * both. Capturing the result therefore has to be exact — the old approach of\n * fishing an issue URL out of free-form prose only ever worked for GitHub.\n *\n * The block mirrors the `<issue-body>` convention already used by the phase\n * templates, which keeps a multi-line markdown body readable and free of the\n * escaping a JSON payload would demand.\n */\n\n/** Wrapper the agent must emit around the draft. */\nexport const DRAFT_TAG = 'issue-draft';\n\n/** Failure to capture a draft from the agent output. */\nexport class IssueDraftParseError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'IssueDraftParseError';\n }\n}\n\n/**\n * Last occurrence of `<tag>...</tag>` in `source`, or `null`.\n *\n * The last one wins: an agent that shows the format before filling it in (or\n * restates the draft after a correction) would otherwise have its first,\n * throwaway attempt captured. The closing tag is searched from the end for the\n * same reason a body may legitimately quote one.\n */\nfunction extractTag(source: string, tag: string): string | null {\n const open = `<${tag}>`;\n const close = `</${tag}>`;\n\n const start = source.lastIndexOf(open);\n if (start === -1) return null;\n\n const end = source.lastIndexOf(close);\n if (end === -1 || end < start) return null;\n\n return source.slice(start + open.length, end);\n}\n\nfunction parseLabels(raw: string | null): string[] {\n if (raw === null) return [];\n return raw\n .split(',')\n .map((label) => label.trim())\n .filter((label) => label.length > 0 && label.toLowerCase() !== '(none)');\n}\n\n/**\n * Read the draft the agent produced.\n *\n * @throws IssueDraftParseError when the block is absent or has no usable\n * title/body, so a malformed answer fails loudly instead of creating an empty\n * Issue.\n */\nexport function parseIssueDraft(output: string): IssueDraft {\n const normalized = output.replace(/\\r\\n?/g, '\\n');\n const block = extractTag(normalized, DRAFT_TAG);\n\n if (block === null) {\n throw new IssueDraftParseError(\n `The agent did not emit an <${DRAFT_TAG}> block, so there is no Issue to create. ` +\n `Output was: ${normalized.trim().slice(0, 300) || '(empty)'}`,\n );\n }\n\n const title = (extractTag(block, 'title') ?? '').trim();\n const body = (extractTag(block, 'body') ?? '').trim();\n\n if (title.length === 0) {\n throw new IssueDraftParseError(`The <${DRAFT_TAG}> block has no <title>.`);\n }\n if (body.length === 0) {\n throw new IssueDraftParseError(`The <${DRAFT_TAG}> block has no <body>.`);\n }\n\n // Optional, and deliberately so: a repository with no Issue Types and no\n // templates emits neither, and the draft is exactly what it was before.\n const type = (extractTag(block, 'type') ?? '').trim();\n const template = (extractTag(block, 'template') ?? '').trim();\n\n return {\n title,\n body,\n labels: parseLabels(extractTag(block, 'labels')),\n ...(type === '' || type.toLowerCase() === '(none)' ? {} : { type }),\n ...(template === '' || template.toLowerCase() === '(none)' ? {} : { template }),\n };\n}\n","import type { LabelDefinition } from '../policy/types.js';\nimport { run } from '../utils/shell.js';\n\n/**\n * Reconciling the labels an agent suggested with the ones the repository\n * actually has.\n *\n * A workflow tool has no business changing a repository's taxonomy. Labels are\n * governance: a team that deleted `high`/`medium`/`low` in favour of a native\n * priority field, or `bug`/`enhancement` in favour of Issue Types, made a\n * decision — and a generator that silently recreates them undoes it, quietly and\n * repository-wide.\n *\n * So the default is to **validate, never create**. `policy.issues.allowLabelCreation`\n * restores the old behaviour for repositories that want it.\n */\n\nexport interface LabelReconciliation {\n /** Labels to apply, in the repository's own casing. */\n labels: string[];\n /** Suggested labels the repository does not have. */\n missing: string[];\n}\n\n/**\n * Intersect the suggested labels with the repository's real ones.\n *\n * Matching is case-insensitive and the result carries the **repository's**\n * casing: `gh issue create --label Bug` fails on a repository whose label is\n * `bug`, and an agent has no way to know which one it is.\n *\n * An empty `known` means the labels could not be read at all — no policy, no\n * `gh`, no network. That is not evidence the labels do not exist, so the\n * suggestions pass through untouched and the behaviour is the one that shipped\n * before this layer. Refusing to label an issue because discovery was offline\n * would be the wrong failure.\n */\nexport function reconcileLabels(\n suggested: string[],\n known: LabelDefinition[],\n): LabelReconciliation {\n if (known.length === 0) {\n return { labels: [...suggested], missing: [] };\n }\n\n const canonical = new Map(known.map((label) => [label.name.toLowerCase(), label.name]));\n\n const labels: string[] = [];\n const missing: string[] = [];\n for (const suggestion of suggested) {\n const match = canonical.get(suggestion.trim().toLowerCase());\n if (match === undefined) {\n if (!missing.includes(suggestion)) missing.push(suggestion);\n continue;\n }\n if (!labels.includes(match)) labels.push(match);\n }\n\n return { labels, missing };\n}\n\n/**\n * Create the labels the repository is missing, for\n * `policy.issues.allowLabelCreation: true`.\n *\n * Best-effort per label: one that cannot be created is reported and skipped, so\n * a single failure does not cost the Issue. Returns the labels that now exist.\n */\nexport async function createMissingLabels(\n names: string[],\n warn: (message: string) => void,\n): Promise<string[]> {\n const created: string[] = [];\n\n for (const name of names) {\n const result = await run('gh', ['label', 'create', name]);\n if (result.exitCode === 0) {\n created.push(name);\n continue;\n }\n // gh answers \"already exists\" when a label was created between the\n // discovery and now, which is a success for our purposes.\n const detail = (result.stderr || result.stdout).trim();\n if (/already exists/i.test(detail)) {\n created.push(name);\n continue;\n }\n warn(`Could not create label \"${name}\": ${detail || 'gh label create failed'}`);\n }\n\n return created;\n}\n","import { loadIssuesConfig, loadPolicyConfig } from '../config.js';\nimport { DEFAULT_HEADLESS_TIMEOUT_MS, runHeadless } from '../core/headless.js';\nimport { applyPlaceholders, loadPrompt } from '../core/prompt-resolver.js';\nimport { publishPhaseMetrics } from '../core/session-metrics.js';\nimport { isoNow } from '../core/state-manager.js';\nimport { getGlobalTimeout } from '../core/verbose.js';\nimport { ensureProvidersRegistered } from '../issues/bootstrap.js';\nimport { localIssueRef } from '../issues/context.js';\nimport { IssueDraftParseError, parseIssueDraft } from '../issues/draft.js';\nimport { createMissingLabels, reconcileLabels } from '../issues/label-policy.js';\nimport { getProvider } from '../issues/registry.js';\nimport type { Issue, IssueDraft, IssueGenerateTarget } from '../issues/types.js';\nimport { loadRepositoryPolicy } from '../policy/index.js';\nimport { resolvePolicyPlaceholders } from '../policy/placeholders.js';\nimport { resolveProjectPaths } from '../storage/resolve.js';\nimport { printError, printInfo, printSuccess, printWarning } from '../ui/logger.js';\n\n/** Human-readable pointer to a created Issue. */\nfunction issueLocation(issue: Issue): string {\n return issue.remoteRef ?? localIssueRef(issue.id);\n}\n\n/**\n * Ask the agent for the Issue content.\n *\n * The agent only drafts: creation belongs to the providers, so the same draft\n * can be persisted to GitHub, to the local files, or to both.\n */\nasync function draftIssue(promptText: string): Promise<IssueDraft> {\n // The duplicate check reads the local issues, which live in the global\n // storage: the path has to be handed over (and allowed) explicitly, since it\n // is outside the working directory the agent is started in.\n const { issuesDir } = await resolveProjectPaths();\n\n const template = await loadPrompt('generate');\n const prompt = applyPlaceholders(template, {\n // The repository's own conventions. Empty when it declares none, which is\n // what keeps the rendered prompt identical to the pre-policy one.\n ...(await resolvePolicyPlaceholders()),\n __USER_PROMPT__: promptText,\n __LOCAL_ISSUES_DIR__: issuesDir,\n });\n\n const startedAtMs = Date.now();\n const result = await runHeadless({\n prompt,\n maxTurns: 15,\n timeout: getGlobalTimeout() ?? DEFAULT_HEADLESS_TIMEOUT_MS,\n // json (not text) so the CLI reports usage: the envelope's `result` field\n // carries the same assistant text parseIssueDraft() already consumed.\n outputFormat: 'json',\n allowedTools: ['Bash', 'Read', 'Glob', 'Grep'],\n addDirs: [issuesDir],\n statusMessage: 'Drafting issue...',\n phase: 'generate',\n permission: 'workspace',\n });\n // Before the success check: the tokens were spent either way.\n publishPhaseMetrics('generate', result.cost, startedAtMs, result.agent?.provider);\n\n if (!result.success) {\n throw new Error(`Issue creation failed: ${result.error}`);\n }\n\n return parseIssueDraft(result.result);\n}\n\n/**\n * Create the GitHub Issue and mirror it locally.\n *\n * The remote comes first on purpose: it owns the identifier, and the mirror has\n * to reuse it so both copies describe one demand. A remote failure therefore\n * leaves nothing behind, and a mirror failure is reported with the remote\n * reference that already exists.\n */\nasync function createBoth(draft: IssueDraft): Promise<Issue[]> {\n const remote = await getProvider('github').create(draft);\n\n try {\n const mirror = await getProvider('local').create({\n ...draft,\n id: remote.id,\n ...(remote.remoteRef\n ? {\n remote: {\n provider: 'github' as const,\n ref: remote.remoteRef,\n syncedAt: isoNow(),\n syncedContentHash: remote.contentHash,\n },\n }\n : {}),\n });\n return [remote, mirror];\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n throw new Error(\n `GitHub issue created at ${issueLocation(remote)}, but the local mirror was not written: ` +\n `${message}. Nothing else was persisted; re-run with --local to create the mirror.`,\n );\n }\n}\n\nasync function createIssues(target: IssueGenerateTarget, draft: IssueDraft): Promise<Issue[]> {\n if (target === 'both') {\n return createBoth(draft);\n }\n return [await getProvider(target).create(draft)];\n}\n\n/**\n * Reconcile the draft's labels with the ones the repository really has.\n *\n * Two failures are being prevented here, and only one of them is cosmetic.\n * GitHub rejects an issue whose label does not exist, so an invented label costs\n * the whole Issue. And a tool that creates the label instead rewrites a taxonomy\n * the team may have curated deliberately — which is worse, because it succeeds.\n *\n * Never throws: a policy that could not be resolved leaves the draft untouched.\n */\nasync function applyLabelPolicy(draft: IssueDraft): Promise<IssueDraft> {\n let known: Awaited<ReturnType<typeof loadRepositoryPolicy>>['issues']['labels'];\n let allowCreation: boolean;\n try {\n const policy = await loadRepositoryPolicy();\n known = policy.issues.labels;\n const config = await loadPolicyConfig({ projectRoot: policy.root });\n allowCreation = config.issues.allowLabelCreation ?? false;\n } catch {\n return draft;\n }\n\n const { labels, missing } = reconcileLabels(draft.labels, known);\n if (missing.length === 0) {\n return { ...draft, labels };\n }\n\n if (!allowCreation) {\n printWarning(\n `Dropping ${missing.length} label(s) this repository does not have: ${missing.join(', ')}. ` +\n 'Issue Flow does not create labels; set policy.issues.allowLabelCreation to change that.',\n );\n return { ...draft, labels };\n }\n\n const created = await createMissingLabels(missing, printWarning);\n return { ...draft, labels: [...labels, ...created] };\n}\n\nexport async function runGenerate(\n promptText: string,\n target?: IssueGenerateTarget,\n): Promise<number> {\n const config = await loadIssuesConfig();\n const destination = target ?? config.defaultGenerateTarget;\n\n ensureProvidersRegistered();\n\n let draft: IssueDraft;\n try {\n draft = await draftIssue(promptText);\n } catch (err) {\n if (err instanceof IssueDraftParseError) {\n printError(`Could not read the generated Issue draft: ${err.message}`);\n } else {\n printError(err instanceof Error ? err.message : String(err));\n }\n return 1;\n }\n\n const finalDraft = await applyLabelPolicy(draft);\n if (finalDraft.template !== undefined) {\n printInfo(`Following the repository's Issue Template: ${finalDraft.template}`);\n }\n if (finalDraft.type !== undefined) {\n printInfo(`Issue Type: ${finalDraft.type}`);\n }\n\n let created: Issue[];\n try {\n created = await createIssues(destination, finalDraft);\n } catch (err) {\n printError(err instanceof Error ? err.message : String(err));\n return 1;\n }\n\n for (const issue of created) {\n printSuccess(`Issue created (${issue.source}): ${issueLocation(issue)}`);\n }\n return 0;\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
committedStoryIds,
|
|
4
|
+
describePreflight,
|
|
5
|
+
getBaseBranch,
|
|
6
|
+
getCommitsSince,
|
|
7
|
+
getCurrentBranch,
|
|
8
|
+
getHeadCommit,
|
|
9
|
+
getProjectRoot,
|
|
10
|
+
getRemoteUrl,
|
|
11
|
+
isWorkingTreeClean,
|
|
12
|
+
localBranchExists,
|
|
13
|
+
normalizeRemoteUrl,
|
|
14
|
+
preflightRepository,
|
|
15
|
+
stripRemoteUrlCredentials
|
|
16
|
+
} from "./chunk-BEZ5BA5U.js";
|
|
17
|
+
import "./chunk-XEYH6ZZI.js";
|
|
18
|
+
export {
|
|
19
|
+
committedStoryIds,
|
|
20
|
+
describePreflight,
|
|
21
|
+
getBaseBranch,
|
|
22
|
+
getCommitsSince,
|
|
23
|
+
getCurrentBranch,
|
|
24
|
+
getHeadCommit,
|
|
25
|
+
getProjectRoot,
|
|
26
|
+
getRemoteUrl,
|
|
27
|
+
isWorkingTreeClean,
|
|
28
|
+
localBranchExists,
|
|
29
|
+
normalizeRemoteUrl,
|
|
30
|
+
preflightRepository,
|
|
31
|
+
stripRemoteUrlCredentials
|
|
32
|
+
};
|
|
33
|
+
//# sourceMappingURL=git-W72QQ6GT.js.map
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
runInit,
|
|
4
|
+
summarizePreflight
|
|
5
|
+
} from "./chunk-F6TOYSYY.js";
|
|
6
|
+
import "./chunk-QJ5UZAH5.js";
|
|
7
|
+
import "./chunk-QK7WUO7A.js";
|
|
8
|
+
import "./chunk-TYRNTSPJ.js";
|
|
9
|
+
import "./chunk-6QYMSNVA.js";
|
|
10
|
+
import "./chunk-3WFZETIM.js";
|
|
11
|
+
import "./chunk-HAKDLR6N.js";
|
|
12
|
+
import "./chunk-BEZ5BA5U.js";
|
|
13
|
+
import "./chunk-67YIU2VB.js";
|
|
14
|
+
import "./chunk-P3LRUNC6.js";
|
|
15
|
+
import "./chunk-G5EK2XRN.js";
|
|
16
|
+
import "./chunk-J3SOTUVI.js";
|
|
17
|
+
import "./chunk-LBGHI4S2.js";
|
|
18
|
+
import "./chunk-7Z4Y3VKM.js";
|
|
19
|
+
import "./chunk-XEYH6ZZI.js";
|
|
20
|
+
export {
|
|
21
|
+
runInit,
|
|
22
|
+
summarizePreflight
|
|
23
|
+
};
|
|
24
|
+
//# sourceMappingURL=init-HTN4V67Q.js.map
|
|
@@ -2,28 +2,40 @@
|
|
|
2
2
|
import {
|
|
3
3
|
PIPELINE_PHASES,
|
|
4
4
|
PipelineManager,
|
|
5
|
-
loadExecutionPlan
|
|
6
|
-
|
|
7
|
-
} from "./chunk-56KS3TFQ.js";
|
|
8
|
-
import {
|
|
9
|
-
describeRunLockOwner,
|
|
10
|
-
isRunLockStale,
|
|
11
|
-
readRunLock
|
|
12
|
-
} from "./chunk-BAUPQXPE.js";
|
|
5
|
+
loadExecutionPlan
|
|
6
|
+
} from "./chunk-OR43364P.js";
|
|
13
7
|
import "./chunk-KMMASOA6.js";
|
|
14
8
|
import {
|
|
15
|
-
|
|
16
|
-
|
|
9
|
+
parseJournal
|
|
10
|
+
} from "./chunk-3BWZO4YI.js";
|
|
11
|
+
import {
|
|
17
12
|
resolveProjectPaths
|
|
18
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-QK7WUO7A.js";
|
|
19
14
|
import {
|
|
20
15
|
printError,
|
|
21
16
|
printInfo,
|
|
22
17
|
printWarning
|
|
23
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-TYRNTSPJ.js";
|
|
19
|
+
import "./chunk-6QYMSNVA.js";
|
|
20
|
+
import "./chunk-3WFZETIM.js";
|
|
21
|
+
import {
|
|
22
|
+
QUEUES_DIR_NAME,
|
|
23
|
+
getIssuePaths
|
|
24
|
+
} from "./chunk-HAKDLR6N.js";
|
|
25
|
+
import "./chunk-BEZ5BA5U.js";
|
|
26
|
+
import "./chunk-67YIU2VB.js";
|
|
27
|
+
import "./chunk-P3LRUNC6.js";
|
|
28
|
+
import "./chunk-G5EK2XRN.js";
|
|
24
29
|
import {
|
|
25
30
|
loadTaskPlan
|
|
26
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-J3SOTUVI.js";
|
|
32
|
+
import {
|
|
33
|
+
describeRunLockOwner,
|
|
34
|
+
isRunLockStale,
|
|
35
|
+
readRunLock
|
|
36
|
+
} from "./chunk-LBGHI4S2.js";
|
|
37
|
+
import "./chunk-7Z4Y3VKM.js";
|
|
38
|
+
import "./chunk-XEYH6ZZI.js";
|
|
27
39
|
|
|
28
40
|
// src/commands/operations.ts
|
|
29
41
|
import { readdir, readFile } from "fs/promises";
|
|
@@ -314,7 +326,7 @@ async function runCancel(issue, options = {}) {
|
|
|
314
326
|
if (target === void 0) return 0;
|
|
315
327
|
const state = await loadIssueState(paths.projectId, target);
|
|
316
328
|
if (state === null || state.plan === null) return 0;
|
|
317
|
-
const { saveTaskPlan, isoNow } = await import("./state-manager-
|
|
329
|
+
const { saveTaskPlan, isoNow } = await import("./state-manager-GQH32C44.js");
|
|
318
330
|
await saveTaskPlan(state.paths.tasksFile, {
|
|
319
331
|
...state.plan,
|
|
320
332
|
runState: {
|
|
@@ -362,4 +374,4 @@ export {
|
|
|
362
374
|
runRuns,
|
|
363
375
|
runStatus
|
|
364
376
|
};
|
|
365
|
-
//# sourceMappingURL=operations-
|
|
377
|
+
//# sourceMappingURL=operations-BCFOBH42.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/commands/operations.ts"],"sourcesContent":["import { readdir, readFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { parseJournal } from '../core/journal.js';\nimport { PIPELINE_PHASES, PipelineManager, type PipelinePhase } from '../core/pipeline.js';\nimport type { SessionEvent, SessionSnapshot } from '../core/session-state.js';\nimport { loadTaskPlan } from '../core/state-manager.js';\nimport { loadExecutionPlan } from '../execution/plan.js';\nimport type { ExecutionPlan } from '../execution/types.js';\nimport { describeRunLockOwner, isRunLockStale, readRunLock } from '../storage/lock.js';\nimport { getIssuePaths, QUEUES_DIR_NAME } from '../storage/paths.js';\nimport { resolveProjectPaths } from '../storage/resolve.js';\nimport type { RunLock } from '../storage/schemas.js';\nimport type { TaskPlan } from '../types.js';\nimport { printError, printInfo, printWarning } from '../ui/logger.js';\n\n/**\n * The operation surface of a long run: `status`, `runs`, `logs`, `pause`,\n * `cancel`.\n *\n * The CLI declared thirteen commands and not one of them answered \"what is\n * running\", \"what failed\", or \"stop what you are doing\". During a six-hour\n * unattended execution the only way to find out was to read JSON by hand, and\n * the only way to stop it was to find the pid.\n *\n * Everything here **reads state that already exists** — `run.lock`,\n * `tasks.json`, `session.json`, `execution-plan.json`, `events.jsonl` — and\n * writes nothing except the two control commands, which do the one thing a\n * person cannot do from outside: ask the owner to stop, gracefully.\n */\n\ninterface ProjectPaths {\n projectId: string;\n projectDir: string;\n issuesDir: string;\n runLockFile: string;\n}\n\n/** One issue's state, assembled from every artifact that knows part of it. */\ninterface IssueState {\n id: string;\n plan: TaskPlan | null;\n snapshot: SessionSnapshot | null;\n nextPhase: PipelinePhase | null;\n paths: ReturnType<typeof getIssuePaths>;\n}\n\nasync function project(): Promise<ProjectPaths | null> {\n try {\n return await resolveProjectPaths();\n } catch (err) {\n printError(`Not inside a usable project: ${err instanceof Error ? err.message : String(err)}`);\n return null;\n }\n}\n\nasync function readJson<T>(path: string): Promise<T | null> {\n try {\n return JSON.parse(await readFile(path, 'utf-8')) as T;\n } catch {\n return null;\n }\n}\n\nasync function loadIssueState(projectId: string, id: string): Promise<IssueState | null> {\n let paths: ReturnType<typeof getIssuePaths>;\n try {\n paths = getIssuePaths(projectId, id);\n } catch {\n return null;\n }\n\n let plan: TaskPlan | null = null;\n try {\n plan = await loadTaskPlan(paths.tasksFile);\n } catch {\n plan = null;\n }\n if (plan === null) return null;\n\n const snapshot = await readJson<SessionSnapshot>(paths.sessionFile);\n const phases: PipelinePhase[] =\n plan.noBranch === true\n ? PIPELINE_PHASES.filter((phase) => phase !== 'pr')\n : [...PIPELINE_PHASES];\n if (plan.prReview?.enabled === true) phases.push('pr-review');\n\n return {\n id,\n plan,\n snapshot,\n nextPhase: new PipelineManager(plan, paths.tasksFile, phases).getNextPhase(),\n paths,\n };\n}\n\n/** Every issue this project has state for, newest attempt first. */\nasync function allIssues(paths: ProjectPaths): Promise<IssueState[]> {\n let ids: string[];\n try {\n ids = await readdir(paths.issuesDir);\n } catch {\n return [];\n }\n\n const states: IssueState[] = [];\n for (const id of ids) {\n const state = await loadIssueState(paths.projectId, id);\n if (state !== null) states.push(state);\n }\n return states.sort((a, b) => attemptedAt(b.plan) - attemptedAt(a.plan));\n}\n\nfunction attemptedAt(plan: TaskPlan | null): number {\n const at = Date.parse(plan?.lastAttemptAt ?? '');\n return Number.isNaN(at) ? 0 : at;\n}\n\n/** Every queue plan of the project. */\nasync function allQueues(paths: ProjectPaths): Promise<ExecutionPlan[]> {\n const dir = join(paths.projectDir, QUEUES_DIR_NAME);\n let entries: string[];\n try {\n entries = await readdir(dir);\n } catch {\n return [];\n }\n\n const plans: ExecutionPlan[] = [];\n for (const entry of entries) {\n const plan = await loadExecutionPlan(join(dir, entry, 'execution-plan.json'));\n if (plan !== null) plans.push(plan);\n }\n return plans;\n}\n\n/** `2m 13s ago`, or `never`. */\nfunction since(at: string | null | undefined, now = Date.now()): string {\n if (at === null || at === undefined || at === '') return 'never';\n const parsed = Date.parse(at);\n if (Number.isNaN(parsed)) return 'unknown';\n return `${formatSeconds(Math.max(0, Math.round((now - parsed) / 1000)))} ago`;\n}\n\nfunction formatSeconds(total: number): string {\n if (total < 60) return `${total}s`;\n const minutes = Math.floor(total / 60);\n if (minutes < 60) return `${minutes}m ${total % 60}s`;\n const hours = Math.floor(minutes / 60);\n return `${hours}h ${minutes % 60}m`;\n}\n\n/* ── status ─────────────────────────────────────────────────────────────── */\n\nexport interface StatusOptions {\n /** Emit the assembled state as JSON instead of a screen. */\n json?: boolean;\n}\n\n/**\n * One screen answering \"what is happening right now\".\n *\n * The three artifacts each know part of it and none knows all of it: the lock\n * knows *who* and *how long since it last said anything*, the task plan knows\n * *which phase and which attempt*, and the queue plan knows *where in the\n * queue*. Reading them together is the whole command.\n */\nexport async function runStatus(issue?: string, options: StatusOptions = {}): Promise<number> {\n const paths = await project();\n if (paths === null) return 1;\n\n const lock = await readRunLock(paths.runLockFile);\n const issues =\n issue === undefined\n ? await allIssues(paths)\n : [await loadIssueState(paths.projectId, issue)].filter(\n (state): state is IssueState => state !== null,\n );\n const queues = await allQueues(paths);\n\n if (options.json === true) {\n printInfo(\n JSON.stringify(\n {\n owner: lock,\n ownerStale: lock === null ? null : isRunLockStale(lock),\n issues: issues.map((state) => ({\n id: state.id,\n issueStatus: state.plan?.issueStatus ?? null,\n runState: state.plan?.runState ?? null,\n nextPhase: state.nextPhase,\n branch: state.plan?.branchName ?? null,\n lastActivityAt: state.snapshot?.updatedAt ?? state.plan?.lastAttemptAt ?? null,\n retries: state.snapshot?.execution.retries ?? null,\n })),\n queues: queues.map((plan) => ({\n id: plan.id,\n status: plan.status,\n issues: plan.issues.map((entry) => ({\n id: entry.id,\n status: entry.status,\n attempts: entry.attempts,\n blockedReason: entry.blockedReason,\n })),\n })),\n },\n null,\n 2,\n ),\n );\n return 0;\n }\n\n printOwner(lock);\n\n if (issues.length === 0) {\n printInfo('No issue in this project has any state yet.');\n return 0;\n }\n\n for (const state of issues.slice(0, issue === undefined ? 5 : 1)) {\n printIssueLine(state);\n }\n\n for (const plan of queues) {\n if (plan.status === 'completed') continue;\n printInfo(`Queue ${plan.id} — ${plan.status}`);\n for (const entry of plan.issues) {\n const attempts = entry.attempts > 0 ? `, ${entry.attempts} attempt(s)` : '';\n const blocked = entry.blockedReason === null ? '' : ` — ${entry.blockedReason}`;\n printInfo(` #${entry.id} ${entry.status}${attempts}${blocked}`);\n }\n }\n\n return 0;\n}\n\nfunction printOwner(lock: RunLock | null): void {\n if (lock === null) {\n printInfo('Nothing is running in this project (no run.lock).');\n return;\n }\n if (isRunLockStale(lock)) {\n printWarning(`A stale lock is left behind: ${describeRunLockOwner(lock)}.`);\n printInfo('It will be taken over by the next run or resume.');\n return;\n }\n printInfo(`Running: ${describeRunLockOwner(lock)}`);\n printInfo(`Last heartbeat ${since(lock.lastHeartbeatAt)}.`);\n}\n\nfunction printIssueLine(state: IssueState): void {\n const plan = state.plan;\n const run = plan?.runState;\n const phase = run?.currentPhase ?? state.nextPhase ?? '—';\n const attempt = run !== undefined && run.attempt > 0 ? `, attempt ${run.attempt}` : '';\n const retries = state.snapshot?.execution.retries;\n const retryLine = retries === undefined || retries === 0 ? '' : `, ${retries} retry(ies)`;\n const activity = since(state.snapshot?.updatedAt ?? plan?.lastAttemptAt);\n\n printInfo(\n `#${state.id} ${plan?.issueStatus ?? 'unknown'}${run === undefined ? '' : ` / ${run.status}`}` +\n ` — phase ${phase}${attempt}${retryLine}; last activity ${activity}`,\n );\n if (run?.blockedReason !== null && run?.blockedReason !== undefined) {\n printWarning(` Blocked: ${run.blockedReason}`);\n }\n}\n\n/* ── runs ───────────────────────────────────────────────────────────────── */\n\n/**\n * The history: what has been run in this project, how it ended, and why.\n *\n * Deliberately built from the same artifacts `status` reads rather than from a\n * separate history file — a second record of the truth is a second thing that\n * can be wrong.\n */\nexport async function runRuns(): Promise<number> {\n const paths = await project();\n if (paths === null) return 1;\n\n const issues = await allIssues(paths);\n if (issues.length === 0) {\n printInfo('No runs recorded for this project yet.');\n return 0;\n }\n\n for (const state of issues) {\n const plan = state.plan;\n if (plan === null) continue;\n const duration =\n state.snapshot?.elapsedSeconds === null || state.snapshot?.elapsedSeconds === undefined\n ? '—'\n : formatSeconds(state.snapshot.elapsedSeconds);\n const cause =\n plan.lastError === null ? '' : ` — ${plan.lastError.message.split('\\n')[0] ?? ''}`;\n\n printInfo(\n `#${state.id.padEnd(6)} ${plan.issueStatus.padEnd(12)} ${duration.padEnd(10)} ` +\n `last attempt ${since(plan.lastAttemptAt)}${cause}`,\n );\n }\n return 0;\n}\n\n/* ── logs ───────────────────────────────────────────────────────────────── */\n\nexport interface LogsOptions {\n /** Only these event types (`retry`, `phase:end`, …). Empty means all. */\n kind?: string[];\n /** Keep reading as the journal grows. */\n follow?: boolean;\n /** How many entries to show before following. */\n tail?: number;\n /** Polling interval of `--follow`, for tests. */\n pollMs?: number;\n /** Stops `--follow`. */\n signal?: AbortSignal;\n}\n\nconst DEFAULT_TAIL = 50;\nconst FOLLOW_POLL_MS = 1000;\n\n/**\n * Read the journal, filtered and readable.\n *\n * The journal is the only record that keeps *what happened in which order* —\n * the snapshot folded it away — so this is the command that answers \"what was\n * it doing at 3am\". `--kind retry,failover` is the filter that matters most:\n * on a six-hour run those are a handful of lines among thousands.\n */\nexport async function runLogs(issue?: string, options: LogsOptions = {}): Promise<number> {\n const paths = await project();\n if (paths === null) return 1;\n\n const target = issue ?? (await allIssues(paths))[0]?.id;\n if (target === undefined) {\n printInfo('No issue in this project has a journal yet.');\n return 0;\n }\n\n const issuePaths = getIssuePaths(paths.projectId, target);\n const kinds = new Set(options.kind ?? []);\n const tail = options.tail ?? DEFAULT_TAIL;\n\n const shown = new Set<number>();\n const render = (content: string, limit: number | null): void => {\n const entries = parseJournal(content).filter(\n (entry) => kinds.size === 0 || kinds.has(entry.event.type),\n );\n const slice = limit === null ? entries : entries.slice(-limit);\n for (const entry of slice) {\n if (shown.has(entry.seq)) continue;\n shown.add(entry.seq);\n printInfo(formatEvent(entry.seq, entry.event));\n }\n };\n\n const readAll = async (): Promise<string> =>\n `${await readIfPresent(issuePaths.rotatedEventsFile)}${await readIfPresent(issuePaths.eventsFile)}`;\n\n const initial = await readAll();\n if (initial === '') {\n printInfo(\n `Issue #${target} has no journal. Enable it with resilience.journal.enabled or --continuous.`,\n );\n return 0;\n }\n render(initial, tail);\n\n if (options.follow !== true) return 0;\n\n const poll = options.pollMs ?? FOLLOW_POLL_MS;\n const signal = options.signal;\n while (signal === undefined || !signal.aborted) {\n await sleep(poll, signal);\n if (signal?.aborted === true) break;\n render(await readAll(), null);\n }\n return 0;\n}\n\nfunction formatEvent(seq: number, event: SessionEvent): string {\n const at = 'at' in event ? event.at : '';\n const detail = describeEvent(event);\n return `${String(seq).padStart(5)} ${at} ${event.type}${detail === '' ? '' : ` — ${detail}`}`;\n}\n\nfunction describeEvent(event: SessionEvent): string {\n switch (event.type) {\n case 'phase:start':\n return event.phase;\n case 'phase:end':\n return `${event.phase} ${event.success ? 'ok' : `failed: ${event.error ?? ''}`}`;\n case 'retry':\n return `attempt ${event.attempt}, ${event.delaySeconds}s — ${event.reason}`;\n case 'log':\n return `${event.level}: ${event.message}`;\n case 'session:end':\n return event.status;\n default:\n return '';\n }\n}\n\nasync function readIfPresent(path: string): Promise<string> {\n try {\n return await readFile(path, 'utf-8');\n } catch {\n return '';\n }\n}\n\nfunction sleep(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise((resolve) => {\n const timer = setTimeout(resolve, ms);\n timer.unref?.();\n signal?.addEventListener('abort', () => {\n clearTimeout(timer);\n resolve();\n });\n });\n}\n\n/* ── pause and cancel ───────────────────────────────────────────────────── */\n\nexport interface ControlOptions {\n /** Injectable signaller, so a test never signals a real process. */\n signalProcess?: (pid: number, signal: NodeJS.Signals) => void;\n}\n\n/**\n * Ask the running owner to stop, gracefully.\n *\n * `SIGTERM` is the whole implementation, and deliberately so: the owner already\n * knows how to stop well (`core/shutdown.ts` writes the checkpoint, stops the\n * agent with a grace period and closes the journal). Reimplementing any of that\n * from outside the process would be a second, worse version of it.\n */\nexport async function runPause(options: ControlOptions = {}): Promise<number> {\n return stopOwner('paused', options);\n}\n\n/**\n * Stop the run and mark it so a later `resume` does not pick it up silently.\n *\n * The difference from `pause` is not in how the process ends — it ends the same\n * way — but in what the state says afterwards: a paused run is meant to be\n * resumed, a cancelled one is not.\n */\nexport async function runCancel(issue?: string, options: ControlOptions = {}): Promise<number> {\n const code = await stopOwner('cancelled', options);\n if (code !== 0) return code;\n\n const paths = await project();\n if (paths === null) return 1;\n\n const target = issue ?? (await allIssues(paths))[0]?.id;\n if (target === undefined) return 0;\n\n const state = await loadIssueState(paths.projectId, target);\n if (state === null || state.plan === null) return 0;\n\n const { saveTaskPlan, isoNow } = await import('../core/state-manager.js');\n await saveTaskPlan(state.paths.tasksFile, {\n ...state.plan,\n runState: {\n currentPhase: state.plan.runState?.currentPhase ?? null,\n attempt: state.plan.runState?.attempt ?? 0,\n owner: null,\n status: 'blocked',\n blockedReason: 'Cancelled by the user',\n lastHeartbeatAt: isoNow(),\n },\n });\n printInfo(`Issue #${target} is marked cancelled. A resume will report it instead of running it.`);\n return 0;\n}\n\nasync function stopOwner(intent: string, options: ControlOptions): Promise<number> {\n const paths = await project();\n if (paths === null) return 1;\n\n const lock = await readRunLock(paths.runLockFile);\n if (lock === null) {\n printInfo('Nothing is running in this project.');\n return 0;\n }\n if (isRunLockStale(lock)) {\n printWarning(`The lock is stale (${describeRunLockOwner(lock)}); nothing to stop.`);\n return 0;\n }\n\n const send =\n options.signalProcess ?? ((pid: number, signal: NodeJS.Signals) => process.kill(pid, signal));\n try {\n send(lock.pid, 'SIGTERM');\n } catch (err) {\n printError(\n `Could not signal pid ${lock.pid}: ${err instanceof Error ? err.message : String(err)}`,\n );\n return 1;\n }\n\n printInfo(\n `Asked pid ${lock.pid} to stop (${intent}). It writes a checkpoint, stops the agent and closes its journal before exiting.`,\n );\n return 0;\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/commands/operations.ts"],"sourcesContent":["import { readdir, readFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { parseJournal } from '../core/journal.js';\nimport { PIPELINE_PHASES, PipelineManager, type PipelinePhase } from '../core/pipeline.js';\nimport type { SessionEvent, SessionSnapshot } from '../core/session-state.js';\nimport { loadTaskPlan } from '../core/state-manager.js';\nimport { loadExecutionPlan } from '../execution/plan.js';\nimport type { ExecutionPlan } from '../execution/types.js';\nimport { describeRunLockOwner, isRunLockStale, readRunLock } from '../storage/lock.js';\nimport { getIssuePaths, QUEUES_DIR_NAME } from '../storage/paths.js';\nimport { resolveProjectPaths } from '../storage/resolve.js';\nimport type { RunLock } from '../storage/schemas.js';\nimport type { TaskPlan } from '../types.js';\nimport { printError, printInfo, printWarning } from '../ui/logger.js';\n\n/**\n * The operation surface of a long run: `status`, `runs`, `logs`, `pause`,\n * `cancel`.\n *\n * The CLI declared thirteen commands and not one of them answered \"what is\n * running\", \"what failed\", or \"stop what you are doing\". During a six-hour\n * unattended execution the only way to find out was to read JSON by hand, and\n * the only way to stop it was to find the pid.\n *\n * Everything here **reads state that already exists** — `run.lock`,\n * `tasks.json`, `session.json`, `execution-plan.json`, `events.jsonl` — and\n * writes nothing except the two control commands, which do the one thing a\n * person cannot do from outside: ask the owner to stop, gracefully.\n */\n\ninterface ProjectPaths {\n projectId: string;\n projectDir: string;\n issuesDir: string;\n runLockFile: string;\n}\n\n/** One issue's state, assembled from every artifact that knows part of it. */\ninterface IssueState {\n id: string;\n plan: TaskPlan | null;\n snapshot: SessionSnapshot | null;\n nextPhase: PipelinePhase | null;\n paths: ReturnType<typeof getIssuePaths>;\n}\n\nasync function project(): Promise<ProjectPaths | null> {\n try {\n return await resolveProjectPaths();\n } catch (err) {\n printError(`Not inside a usable project: ${err instanceof Error ? err.message : String(err)}`);\n return null;\n }\n}\n\nasync function readJson<T>(path: string): Promise<T | null> {\n try {\n return JSON.parse(await readFile(path, 'utf-8')) as T;\n } catch {\n return null;\n }\n}\n\nasync function loadIssueState(projectId: string, id: string): Promise<IssueState | null> {\n let paths: ReturnType<typeof getIssuePaths>;\n try {\n paths = getIssuePaths(projectId, id);\n } catch {\n return null;\n }\n\n let plan: TaskPlan | null = null;\n try {\n plan = await loadTaskPlan(paths.tasksFile);\n } catch {\n plan = null;\n }\n if (plan === null) return null;\n\n const snapshot = await readJson<SessionSnapshot>(paths.sessionFile);\n const phases: PipelinePhase[] =\n plan.noBranch === true\n ? PIPELINE_PHASES.filter((phase) => phase !== 'pr')\n : [...PIPELINE_PHASES];\n if (plan.prReview?.enabled === true) phases.push('pr-review');\n\n return {\n id,\n plan,\n snapshot,\n nextPhase: new PipelineManager(plan, paths.tasksFile, phases).getNextPhase(),\n paths,\n };\n}\n\n/** Every issue this project has state for, newest attempt first. */\nasync function allIssues(paths: ProjectPaths): Promise<IssueState[]> {\n let ids: string[];\n try {\n ids = await readdir(paths.issuesDir);\n } catch {\n return [];\n }\n\n const states: IssueState[] = [];\n for (const id of ids) {\n const state = await loadIssueState(paths.projectId, id);\n if (state !== null) states.push(state);\n }\n return states.sort((a, b) => attemptedAt(b.plan) - attemptedAt(a.plan));\n}\n\nfunction attemptedAt(plan: TaskPlan | null): number {\n const at = Date.parse(plan?.lastAttemptAt ?? '');\n return Number.isNaN(at) ? 0 : at;\n}\n\n/** Every queue plan of the project. */\nasync function allQueues(paths: ProjectPaths): Promise<ExecutionPlan[]> {\n const dir = join(paths.projectDir, QUEUES_DIR_NAME);\n let entries: string[];\n try {\n entries = await readdir(dir);\n } catch {\n return [];\n }\n\n const plans: ExecutionPlan[] = [];\n for (const entry of entries) {\n const plan = await loadExecutionPlan(join(dir, entry, 'execution-plan.json'));\n if (plan !== null) plans.push(plan);\n }\n return plans;\n}\n\n/** `2m 13s ago`, or `never`. */\nfunction since(at: string | null | undefined, now = Date.now()): string {\n if (at === null || at === undefined || at === '') return 'never';\n const parsed = Date.parse(at);\n if (Number.isNaN(parsed)) return 'unknown';\n return `${formatSeconds(Math.max(0, Math.round((now - parsed) / 1000)))} ago`;\n}\n\nfunction formatSeconds(total: number): string {\n if (total < 60) return `${total}s`;\n const minutes = Math.floor(total / 60);\n if (minutes < 60) return `${minutes}m ${total % 60}s`;\n const hours = Math.floor(minutes / 60);\n return `${hours}h ${minutes % 60}m`;\n}\n\n/* ── status ─────────────────────────────────────────────────────────────── */\n\nexport interface StatusOptions {\n /** Emit the assembled state as JSON instead of a screen. */\n json?: boolean;\n}\n\n/**\n * One screen answering \"what is happening right now\".\n *\n * The three artifacts each know part of it and none knows all of it: the lock\n * knows *who* and *how long since it last said anything*, the task plan knows\n * *which phase and which attempt*, and the queue plan knows *where in the\n * queue*. Reading them together is the whole command.\n */\nexport async function runStatus(issue?: string, options: StatusOptions = {}): Promise<number> {\n const paths = await project();\n if (paths === null) return 1;\n\n const lock = await readRunLock(paths.runLockFile);\n const issues =\n issue === undefined\n ? await allIssues(paths)\n : [await loadIssueState(paths.projectId, issue)].filter(\n (state): state is IssueState => state !== null,\n );\n const queues = await allQueues(paths);\n\n if (options.json === true) {\n printInfo(\n JSON.stringify(\n {\n owner: lock,\n ownerStale: lock === null ? null : isRunLockStale(lock),\n issues: issues.map((state) => ({\n id: state.id,\n issueStatus: state.plan?.issueStatus ?? null,\n runState: state.plan?.runState ?? null,\n nextPhase: state.nextPhase,\n branch: state.plan?.branchName ?? null,\n lastActivityAt: state.snapshot?.updatedAt ?? state.plan?.lastAttemptAt ?? null,\n retries: state.snapshot?.execution.retries ?? null,\n })),\n queues: queues.map((plan) => ({\n id: plan.id,\n status: plan.status,\n issues: plan.issues.map((entry) => ({\n id: entry.id,\n status: entry.status,\n attempts: entry.attempts,\n blockedReason: entry.blockedReason,\n })),\n })),\n },\n null,\n 2,\n ),\n );\n return 0;\n }\n\n printOwner(lock);\n\n if (issues.length === 0) {\n printInfo('No issue in this project has any state yet.');\n return 0;\n }\n\n for (const state of issues.slice(0, issue === undefined ? 5 : 1)) {\n printIssueLine(state);\n }\n\n for (const plan of queues) {\n if (plan.status === 'completed') continue;\n printInfo(`Queue ${plan.id} — ${plan.status}`);\n for (const entry of plan.issues) {\n const attempts = entry.attempts > 0 ? `, ${entry.attempts} attempt(s)` : '';\n const blocked = entry.blockedReason === null ? '' : ` — ${entry.blockedReason}`;\n printInfo(` #${entry.id} ${entry.status}${attempts}${blocked}`);\n }\n }\n\n return 0;\n}\n\nfunction printOwner(lock: RunLock | null): void {\n if (lock === null) {\n printInfo('Nothing is running in this project (no run.lock).');\n return;\n }\n if (isRunLockStale(lock)) {\n printWarning(`A stale lock is left behind: ${describeRunLockOwner(lock)}.`);\n printInfo('It will be taken over by the next run or resume.');\n return;\n }\n printInfo(`Running: ${describeRunLockOwner(lock)}`);\n printInfo(`Last heartbeat ${since(lock.lastHeartbeatAt)}.`);\n}\n\nfunction printIssueLine(state: IssueState): void {\n const plan = state.plan;\n const run = plan?.runState;\n const phase = run?.currentPhase ?? state.nextPhase ?? '—';\n const attempt = run !== undefined && run.attempt > 0 ? `, attempt ${run.attempt}` : '';\n const retries = state.snapshot?.execution.retries;\n const retryLine = retries === undefined || retries === 0 ? '' : `, ${retries} retry(ies)`;\n const activity = since(state.snapshot?.updatedAt ?? plan?.lastAttemptAt);\n\n printInfo(\n `#${state.id} ${plan?.issueStatus ?? 'unknown'}${run === undefined ? '' : ` / ${run.status}`}` +\n ` — phase ${phase}${attempt}${retryLine}; last activity ${activity}`,\n );\n if (run?.blockedReason !== null && run?.blockedReason !== undefined) {\n printWarning(` Blocked: ${run.blockedReason}`);\n }\n}\n\n/* ── runs ───────────────────────────────────────────────────────────────── */\n\n/**\n * The history: what has been run in this project, how it ended, and why.\n *\n * Deliberately built from the same artifacts `status` reads rather than from a\n * separate history file — a second record of the truth is a second thing that\n * can be wrong.\n */\nexport async function runRuns(): Promise<number> {\n const paths = await project();\n if (paths === null) return 1;\n\n const issues = await allIssues(paths);\n if (issues.length === 0) {\n printInfo('No runs recorded for this project yet.');\n return 0;\n }\n\n for (const state of issues) {\n const plan = state.plan;\n if (plan === null) continue;\n const duration =\n state.snapshot?.elapsedSeconds === null || state.snapshot?.elapsedSeconds === undefined\n ? '—'\n : formatSeconds(state.snapshot.elapsedSeconds);\n const cause =\n plan.lastError === null ? '' : ` — ${plan.lastError.message.split('\\n')[0] ?? ''}`;\n\n printInfo(\n `#${state.id.padEnd(6)} ${plan.issueStatus.padEnd(12)} ${duration.padEnd(10)} ` +\n `last attempt ${since(plan.lastAttemptAt)}${cause}`,\n );\n }\n return 0;\n}\n\n/* ── logs ───────────────────────────────────────────────────────────────── */\n\nexport interface LogsOptions {\n /** Only these event types (`retry`, `phase:end`, …). Empty means all. */\n kind?: string[];\n /** Keep reading as the journal grows. */\n follow?: boolean;\n /** How many entries to show before following. */\n tail?: number;\n /** Polling interval of `--follow`, for tests. */\n pollMs?: number;\n /** Stops `--follow`. */\n signal?: AbortSignal;\n}\n\nconst DEFAULT_TAIL = 50;\nconst FOLLOW_POLL_MS = 1000;\n\n/**\n * Read the journal, filtered and readable.\n *\n * The journal is the only record that keeps *what happened in which order* —\n * the snapshot folded it away — so this is the command that answers \"what was\n * it doing at 3am\". `--kind retry,failover` is the filter that matters most:\n * on a six-hour run those are a handful of lines among thousands.\n */\nexport async function runLogs(issue?: string, options: LogsOptions = {}): Promise<number> {\n const paths = await project();\n if (paths === null) return 1;\n\n const target = issue ?? (await allIssues(paths))[0]?.id;\n if (target === undefined) {\n printInfo('No issue in this project has a journal yet.');\n return 0;\n }\n\n const issuePaths = getIssuePaths(paths.projectId, target);\n const kinds = new Set(options.kind ?? []);\n const tail = options.tail ?? DEFAULT_TAIL;\n\n const shown = new Set<number>();\n const render = (content: string, limit: number | null): void => {\n const entries = parseJournal(content).filter(\n (entry) => kinds.size === 0 || kinds.has(entry.event.type),\n );\n const slice = limit === null ? entries : entries.slice(-limit);\n for (const entry of slice) {\n if (shown.has(entry.seq)) continue;\n shown.add(entry.seq);\n printInfo(formatEvent(entry.seq, entry.event));\n }\n };\n\n const readAll = async (): Promise<string> =>\n `${await readIfPresent(issuePaths.rotatedEventsFile)}${await readIfPresent(issuePaths.eventsFile)}`;\n\n const initial = await readAll();\n if (initial === '') {\n printInfo(\n `Issue #${target} has no journal. Enable it with resilience.journal.enabled or --continuous.`,\n );\n return 0;\n }\n render(initial, tail);\n\n if (options.follow !== true) return 0;\n\n const poll = options.pollMs ?? FOLLOW_POLL_MS;\n const signal = options.signal;\n while (signal === undefined || !signal.aborted) {\n await sleep(poll, signal);\n if (signal?.aborted === true) break;\n render(await readAll(), null);\n }\n return 0;\n}\n\nfunction formatEvent(seq: number, event: SessionEvent): string {\n const at = 'at' in event ? event.at : '';\n const detail = describeEvent(event);\n return `${String(seq).padStart(5)} ${at} ${event.type}${detail === '' ? '' : ` — ${detail}`}`;\n}\n\nfunction describeEvent(event: SessionEvent): string {\n switch (event.type) {\n case 'phase:start':\n return event.phase;\n case 'phase:end':\n return `${event.phase} ${event.success ? 'ok' : `failed: ${event.error ?? ''}`}`;\n case 'retry':\n return `attempt ${event.attempt}, ${event.delaySeconds}s — ${event.reason}`;\n case 'log':\n return `${event.level}: ${event.message}`;\n case 'session:end':\n return event.status;\n default:\n return '';\n }\n}\n\nasync function readIfPresent(path: string): Promise<string> {\n try {\n return await readFile(path, 'utf-8');\n } catch {\n return '';\n }\n}\n\nfunction sleep(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise((resolve) => {\n const timer = setTimeout(resolve, ms);\n timer.unref?.();\n signal?.addEventListener('abort', () => {\n clearTimeout(timer);\n resolve();\n });\n });\n}\n\n/* ── pause and cancel ───────────────────────────────────────────────────── */\n\nexport interface ControlOptions {\n /** Injectable signaller, so a test never signals a real process. */\n signalProcess?: (pid: number, signal: NodeJS.Signals) => void;\n}\n\n/**\n * Ask the running owner to stop, gracefully.\n *\n * `SIGTERM` is the whole implementation, and deliberately so: the owner already\n * knows how to stop well (`core/shutdown.ts` writes the checkpoint, stops the\n * agent with a grace period and closes the journal). Reimplementing any of that\n * from outside the process would be a second, worse version of it.\n */\nexport async function runPause(options: ControlOptions = {}): Promise<number> {\n return stopOwner('paused', options);\n}\n\n/**\n * Stop the run and mark it so a later `resume` does not pick it up silently.\n *\n * The difference from `pause` is not in how the process ends — it ends the same\n * way — but in what the state says afterwards: a paused run is meant to be\n * resumed, a cancelled one is not.\n */\nexport async function runCancel(issue?: string, options: ControlOptions = {}): Promise<number> {\n const code = await stopOwner('cancelled', options);\n if (code !== 0) return code;\n\n const paths = await project();\n if (paths === null) return 1;\n\n const target = issue ?? (await allIssues(paths))[0]?.id;\n if (target === undefined) return 0;\n\n const state = await loadIssueState(paths.projectId, target);\n if (state === null || state.plan === null) return 0;\n\n const { saveTaskPlan, isoNow } = await import('../core/state-manager.js');\n await saveTaskPlan(state.paths.tasksFile, {\n ...state.plan,\n runState: {\n currentPhase: state.plan.runState?.currentPhase ?? null,\n attempt: state.plan.runState?.attempt ?? 0,\n owner: null,\n status: 'blocked',\n blockedReason: 'Cancelled by the user',\n lastHeartbeatAt: isoNow(),\n },\n });\n printInfo(`Issue #${target} is marked cancelled. A resume will report it instead of running it.`);\n return 0;\n}\n\nasync function stopOwner(intent: string, options: ControlOptions): Promise<number> {\n const paths = await project();\n if (paths === null) return 1;\n\n const lock = await readRunLock(paths.runLockFile);\n if (lock === null) {\n printInfo('Nothing is running in this project.');\n return 0;\n }\n if (isRunLockStale(lock)) {\n printWarning(`The lock is stale (${describeRunLockOwner(lock)}); nothing to stop.`);\n return 0;\n }\n\n const send =\n options.signalProcess ?? ((pid: number, signal: NodeJS.Signals) => process.kill(pid, signal));\n try {\n send(lock.pid, 'SIGTERM');\n } catch (err) {\n printError(\n `Could not signal pid ${lock.pid}: ${err instanceof Error ? err.message : String(err)}`,\n );\n return 1;\n }\n\n printInfo(\n `Asked pid ${lock.pid} to stop (${intent}). It writes a checkpoint, stops the agent and closes its journal before exiting.`,\n );\n return 0;\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
MANAGED_MARKER,
|
|
4
|
+
cursorPermissionsPath,
|
|
5
|
+
cursorStorageGranted,
|
|
6
|
+
ensureCursorStorageGrant
|
|
7
|
+
} from "./chunk-OOVG2KBY.js";
|
|
8
|
+
import "./chunk-HAKDLR6N.js";
|
|
9
|
+
import "./chunk-BEZ5BA5U.js";
|
|
10
|
+
import "./chunk-7Z4Y3VKM.js";
|
|
11
|
+
import "./chunk-XEYH6ZZI.js";
|
|
12
|
+
export {
|
|
13
|
+
MANAGED_MARKER,
|
|
14
|
+
cursorPermissionsPath,
|
|
15
|
+
cursorStorageGranted,
|
|
16
|
+
ensureCursorStorageGrant
|
|
17
|
+
};
|
|
18
|
+
//# sourceMappingURL=permissions-4AKNQZHK.js.map
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
runPlan
|
|
4
|
+
} from "./chunk-6BENHR2I.js";
|
|
5
|
+
import "./chunk-76MMPJ53.js";
|
|
6
|
+
import "./chunk-REO3PXXO.js";
|
|
7
|
+
import "./chunk-PZTWU277.js";
|
|
8
|
+
import "./chunk-KMMASOA6.js";
|
|
9
|
+
import "./chunk-PHW2YV6N.js";
|
|
10
|
+
import "./chunk-KMRFRXIT.js";
|
|
11
|
+
import "./chunk-CGKLE6WR.js";
|
|
12
|
+
import "./chunk-LEW4QM74.js";
|
|
13
|
+
import "./chunk-3BWZO4YI.js";
|
|
14
|
+
import "./chunk-OOVG2KBY.js";
|
|
15
|
+
import "./chunk-3D4A4B46.js";
|
|
16
|
+
import "./chunk-RVO7L4FX.js";
|
|
17
|
+
import "./chunk-QJ5UZAH5.js";
|
|
18
|
+
import "./chunk-QK7WUO7A.js";
|
|
19
|
+
import "./chunk-TYRNTSPJ.js";
|
|
20
|
+
import "./chunk-6QYMSNVA.js";
|
|
21
|
+
import "./chunk-3WFZETIM.js";
|
|
22
|
+
import "./chunk-HAKDLR6N.js";
|
|
23
|
+
import "./chunk-BEZ5BA5U.js";
|
|
24
|
+
import "./chunk-67YIU2VB.js";
|
|
25
|
+
import "./chunk-P3LRUNC6.js";
|
|
26
|
+
import "./chunk-G5EK2XRN.js";
|
|
27
|
+
import "./chunk-J3SOTUVI.js";
|
|
28
|
+
import "./chunk-LBGHI4S2.js";
|
|
29
|
+
import "./chunk-7Z4Y3VKM.js";
|
|
30
|
+
import "./chunk-XEYH6ZZI.js";
|
|
31
|
+
export {
|
|
32
|
+
runPlan
|
|
33
|
+
};
|
|
34
|
+
//# sourceMappingURL=plan-SIZLGA4V.js.map
|
|
@@ -2,15 +2,25 @@
|
|
|
2
2
|
import "./chunk-3D4A4B46.js";
|
|
3
3
|
import {
|
|
4
4
|
loadRepositoryPolicy
|
|
5
|
-
} from "./chunk-
|
|
6
|
-
import "./chunk-
|
|
7
|
-
import "./chunk-
|
|
5
|
+
} from "./chunk-RVO7L4FX.js";
|
|
6
|
+
import "./chunk-QJ5UZAH5.js";
|
|
7
|
+
import "./chunk-QK7WUO7A.js";
|
|
8
8
|
import {
|
|
9
9
|
printError,
|
|
10
10
|
printInfo,
|
|
11
11
|
printWarning
|
|
12
|
-
} from "./chunk-
|
|
13
|
-
import "./chunk-
|
|
12
|
+
} from "./chunk-TYRNTSPJ.js";
|
|
13
|
+
import "./chunk-6QYMSNVA.js";
|
|
14
|
+
import "./chunk-3WFZETIM.js";
|
|
15
|
+
import "./chunk-HAKDLR6N.js";
|
|
16
|
+
import "./chunk-BEZ5BA5U.js";
|
|
17
|
+
import "./chunk-67YIU2VB.js";
|
|
18
|
+
import "./chunk-P3LRUNC6.js";
|
|
19
|
+
import "./chunk-G5EK2XRN.js";
|
|
20
|
+
import "./chunk-J3SOTUVI.js";
|
|
21
|
+
import "./chunk-LBGHI4S2.js";
|
|
22
|
+
import "./chunk-7Z4Y3VKM.js";
|
|
23
|
+
import "./chunk-XEYH6ZZI.js";
|
|
14
24
|
|
|
15
25
|
// src/commands/policy.ts
|
|
16
26
|
function groupSources(sources) {
|
|
@@ -69,6 +79,10 @@ function render(policy) {
|
|
|
69
79
|
push("Git");
|
|
70
80
|
push(` branchConvention: ${policy.git.branchConvention ?? "(none)"}`);
|
|
71
81
|
push(` commitConvention: ${policy.git.commitConvention ?? "(none)"}`);
|
|
82
|
+
push(` pullRequestTitleConvention: ${policy.git.pullRequestTitleConvention ?? "(none)"}`);
|
|
83
|
+
push(` issueReference: ${policy.git.issueReference ?? "(none)"}`);
|
|
84
|
+
push(` allowedTypes: ${policy.git.allowedTypes?.join(", ") ?? "(none)"}`);
|
|
85
|
+
push(` scopes: ${policy.git.scopes?.join(", ") ?? "(none)"}`);
|
|
72
86
|
push();
|
|
73
87
|
push(`Documents: ${policy.docs.length}`);
|
|
74
88
|
for (const doc of policy.docs) {
|
|
@@ -116,4 +130,4 @@ async function runPolicy(options = {}) {
|
|
|
116
130
|
export {
|
|
117
131
|
runPolicy
|
|
118
132
|
};
|
|
119
|
-
//# sourceMappingURL=policy-
|
|
133
|
+
//# sourceMappingURL=policy-DT6Q6ZVM.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/policy.ts"],"sourcesContent":["import type { PolicySource, RepositoryPolicy } from '../policy/index.js';\nimport { loadRepositoryPolicy } from '../policy/index.js';\nimport { printError, printInfo, printWarning } from '../ui/logger.js';\n\n/**\n * `issue-flow policy [--scope <dir>] [--json]`.\n *\n * The inspection surface of the policy layer, and the only one for now: this\n * issue delivers the foundation, so no prompt and no skill consumes\n * `loadRepositoryPolicy()` yet.\n *\n * It exists for two audiences. A human debugging a wrong discovery reads the\n * default output, where every section names the files it came from. The Agent\n * Skills read `--json`: they are markdown and cannot import TypeScript, so a\n * versioned JSON document on stdout is the only bridge available to them —\n * which is why `schemaVersion` is part of the payload rather than of the CLI.\n *\n * That makes `--json` a **published contract**, not a debugging convenience.\n * `skills/_shared/repository-policy.md` documents the payload, and\n * `policy-parity.test.ts` pins the fields the skills read: renaming or dropping\n * one silently makes every skill fall back to its defaults, and nothing would\n * fail — the two paths would simply start deciding differently. Adding a field\n * is safe (readers ignore what they do not know); removing or renaming one\n * bumps `schemaVersion`.\n */\n\nexport interface PolicyCommandOptions {\n scope?: string;\n json?: boolean;\n}\n\n/** Group the provenance entries by kind, preserving discovery order. */\nfunction groupSources(sources: PolicySource[]): Map<PolicySource['kind'], PolicySource[]> {\n const grouped = new Map<PolicySource['kind'], PolicySource[]>();\n for (const source of sources) {\n const bucket = grouped.get(source.kind);\n if (bucket === undefined) {\n grouped.set(source.kind, [source]);\n } else {\n bucket.push(source);\n }\n }\n return grouped;\n}\n\nfunction describeSource(source: PolicySource): string {\n const where = source.path ?? source.origin;\n const detail = source.detail === null ? '' : ` — ${source.detail}`;\n return `${source.status === 'found' ? '' : `[${source.status}] `}${where}${detail}`;\n}\n\n/**\n * Human-readable rendering.\n *\n * Content is deliberately never printed: a resolved policy carries whole\n * `AGENTS.md` and template bodies, and dumping them would bury the one thing\n * this view is for — which file each value came from. `--json` is where the\n * content lives.\n */\nfunction render(policy: RepositoryPolicy): void {\n const lines: string[] = [];\n const push = (line = ''): void => {\n lines.push(line);\n };\n\n push(`Repository: ${policy.root}`);\n push(`Scope: ${policy.scope ?? '(root)'}`);\n if (!policy.enabled) {\n push();\n push('Policy discovery is disabled (policy.enabled = false).');\n console.log(lines.join('\\n'));\n return;\n }\n\n push();\n push('Issues');\n push(` templates: ${policy.issues.templates.length}`);\n for (const template of policy.issues.templates) {\n const labels = template.labels.length > 0 ? ` labels=[${template.labels.join(', ')}]` : '';\n const type = template.type === null ? '' : ` type=${template.type}`;\n push(` - ${template.path} (${template.format}, ${template.origin})${type}${labels}`);\n }\n push(` types: ${policy.issues.types.length > 0 ? policy.issues.types.join(', ') : '(none)'}`);\n push(` labels: ${policy.issues.labels.length}`);\n for (const label of policy.issues.labels) {\n push(` - ${label.name}${label.description === null ? '' : ` — ${label.description}`}`);\n }\n push(` titleConvention: ${policy.issues.titleConvention ?? '(none)'}`);\n\n push();\n push('Pull Requests');\n push(` template: ${policy.pullRequests.templates[0]?.path ?? '(none)'}`);\n for (const template of policy.pullRequests.templates.slice(1)) {\n push(` - ${template.path}`);\n }\n push(` baseBranch: ${policy.pullRequests.baseBranch ?? '(none)'}`);\n push(` titleConvention: ${policy.pullRequests.titleConvention ?? '(none)'}`);\n\n push();\n push('Git');\n push(` branchConvention: ${policy.git.branchConvention ?? '(none)'}`);\n push(` commitConvention: ${policy.git.commitConvention ?? '(none)'}`);\n push(` pullRequestTitleConvention: ${policy.git.pullRequestTitleConvention ?? '(none)'}`);\n push(` issueReference: ${policy.git.issueReference ?? '(none)'}`);\n push(` allowedTypes: ${policy.git.allowedTypes?.join(', ') ?? '(none)'}`);\n push(` scopes: ${policy.git.scopes?.join(', ') ?? '(none)'}`);\n\n push();\n push(`Documents: ${policy.docs.length}`);\n for (const doc of policy.docs) {\n const scope = doc.scope === '' ? 'root' : doc.scope;\n const from = doc.referencedFrom === null ? '' : ` ← ${doc.referencedFrom}`;\n push(` - ${doc.path} (${doc.kind}, scope=${scope})${from}`);\n }\n\n push();\n push(`CODEOWNERS: ${policy.codeowners === null ? '(none)' : 'found'}`);\n\n push();\n push('Sources');\n for (const [kind, entries] of groupSources(policy.sources)) {\n push(` ${kind}`);\n for (const entry of entries) {\n push(` - ${describeSource(entry)}`);\n }\n }\n\n console.log(lines.join('\\n'));\n}\n\n/**\n * Resolve and print the repository policy. Returns the process exit code.\n *\n * Only a repository that cannot be located at all is a failure: an empty policy\n * is a legitimate — and common — answer, printed like any other.\n */\nexport async function runPolicy(options: PolicyCommandOptions = {}): Promise<number> {\n let policy: RepositoryPolicy;\n try {\n policy = await loadRepositoryPolicy({ scope: options.scope ?? null });\n } catch (err) {\n printError(err instanceof Error ? err.message : String(err));\n return 1;\n }\n\n if (options.json === true) {\n console.log(JSON.stringify(policy, null, 2));\n return 0;\n }\n\n render(policy);\n\n const unavailable = policy.sources.filter((source) => source.status === 'unavailable');\n if (unavailable.length > 0) {\n console.log('');\n printWarning(\n `${unavailable.length} source(s) could not be consulted; the policy above is incomplete.`,\n );\n } else if (policy.sources.length === 0) {\n console.log('');\n printInfo('This repository declares no policy sources; Issue Flow defaults apply.');\n }\n\n return 0;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AAgCA,SAAS,aAAa,SAAoE;AACxF,QAAM,UAAU,oBAAI,IAA0C;AAC9D,aAAW,UAAU,SAAS;AAC5B,UAAM,SAAS,QAAQ,IAAI,OAAO,IAAI;AACtC,QAAI,WAAW,QAAW;AACxB,cAAQ,IAAI,OAAO,MAAM,CAAC,MAAM,CAAC;AAAA,IACnC,OAAO;AACL,aAAO,KAAK,MAAM;AAAA,IACpB;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,eAAe,QAA8B;AACpD,QAAM,QAAQ,OAAO,QAAQ,OAAO;AACpC,QAAM,SAAS,OAAO,WAAW,OAAO,KAAK,WAAM,OAAO,MAAM;AAChE,SAAO,GAAG,OAAO,WAAW,UAAU,KAAK,IAAI,OAAO,MAAM,IAAI,GAAG,KAAK,GAAG,MAAM;AACnF;AAUA,SAAS,OAAO,QAAgC;AAC9C,QAAM,QAAkB,CAAC;AACzB,QAAM,OAAO,CAAC,OAAO,OAAa;AAChC,UAAM,KAAK,IAAI;AAAA,EACjB;AAEA,OAAK,eAAe,OAAO,IAAI,EAAE;AACjC,OAAK,eAAe,OAAO,SAAS,QAAQ,EAAE;AAC9C,MAAI,CAAC,OAAO,SAAS;AACnB,SAAK;AACL,SAAK,wDAAwD;AAC7D,YAAQ,IAAI,MAAM,KAAK,IAAI,CAAC;AAC5B;AAAA,EACF;AAEA,OAAK;AACL,OAAK,QAAQ;AACb,OAAK,gBAAgB,OAAO,OAAO,UAAU,MAAM,EAAE;AACrD,aAAW,YAAY,OAAO,OAAO,WAAW;AAC9C,UAAM,SAAS,SAAS,OAAO,SAAS,IAAI,YAAY,SAAS,OAAO,KAAK,IAAI,CAAC,MAAM;AACxF,UAAM,OAAO,SAAS,SAAS,OAAO,KAAK,SAAS,SAAS,IAAI;AACjE,SAAK,SAAS,SAAS,IAAI,KAAK,SAAS,MAAM,KAAK,SAAS,MAAM,IAAI,IAAI,GAAG,MAAM,EAAE;AAAA,EACxF;AACA,OAAK,aAAa,OAAO,OAAO,MAAM,SAAS,IAAI,OAAO,OAAO,MAAM,KAAK,IAAI,IAAI,QAAQ,EAAE;AAC9F,OAAK,aAAa,OAAO,OAAO,OAAO,MAAM,EAAE;AAC/C,aAAW,SAAS,OAAO,OAAO,QAAQ;AACxC,SAAK,SAAS,MAAM,IAAI,GAAG,MAAM,gBAAgB,OAAO,KAAK,WAAM,MAAM,WAAW,EAAE,EAAE;AAAA,EAC1F;AACA,OAAK,sBAAsB,OAAO,OAAO,mBAAmB,QAAQ,EAAE;AAEtE,OAAK;AACL,OAAK,eAAe;AACpB,OAAK,gBAAgB,OAAO,aAAa,UAAU,CAAC,GAAG,QAAQ,QAAQ,EAAE;AACzE,aAAW,YAAY,OAAO,aAAa,UAAU,MAAM,CAAC,GAAG;AAC7D,SAAK,SAAS,SAAS,IAAI,EAAE;AAAA,EAC/B;AACA,OAAK,sBAAsB,OAAO,aAAa,cAAc,QAAQ,EAAE;AACvE,OAAK,sBAAsB,OAAO,aAAa,mBAAmB,QAAQ,EAAE;AAE5E,OAAK;AACL,OAAK,KAAK;AACV,OAAK,uBAAuB,OAAO,IAAI,oBAAoB,QAAQ,EAAE;AACrE,OAAK,uBAAuB,OAAO,IAAI,oBAAoB,QAAQ,EAAE;AACrE,OAAK,iCAAiC,OAAO,IAAI,8BAA8B,QAAQ,EAAE;AACzF,OAAK,qBAAqB,OAAO,IAAI,kBAAkB,QAAQ,EAAE;AACjE,OAAK,mBAAmB,OAAO,IAAI,cAAc,KAAK,IAAI,KAAK,QAAQ,EAAE;AACzE,OAAK,aAAa,OAAO,IAAI,QAAQ,KAAK,IAAI,KAAK,QAAQ,EAAE;AAE7D,OAAK;AACL,OAAK,cAAc,OAAO,KAAK,MAAM,EAAE;AACvC,aAAW,OAAO,OAAO,MAAM;AAC7B,UAAM,QAAQ,IAAI,UAAU,KAAK,SAAS,IAAI;AAC9C,UAAM,OAAO,IAAI,mBAAmB,OAAO,KAAK,WAAM,IAAI,cAAc;AACxE,SAAK,OAAO,IAAI,IAAI,KAAK,IAAI,IAAI,WAAW,KAAK,IAAI,IAAI,EAAE;AAAA,EAC7D;AAEA,OAAK;AACL,OAAK,eAAe,OAAO,eAAe,OAAO,WAAW,OAAO,EAAE;AAErE,OAAK;AACL,OAAK,SAAS;AACd,aAAW,CAAC,MAAM,OAAO,KAAK,aAAa,OAAO,OAAO,GAAG;AAC1D,SAAK,KAAK,IAAI,EAAE;AAChB,eAAW,SAAS,SAAS;AAC3B,WAAK,SAAS,eAAe,KAAK,CAAC,EAAE;AAAA,IACvC;AAAA,EACF;AAEA,UAAQ,IAAI,MAAM,KAAK,IAAI,CAAC;AAC9B;AAQA,eAAsB,UAAU,UAAgC,CAAC,GAAoB;AACnF,MAAI;AACJ,MAAI;AACF,aAAS,MAAM,qBAAqB,EAAE,OAAO,QAAQ,SAAS,KAAK,CAAC;AAAA,EACtE,SAAS,KAAK;AACZ,eAAW,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAC3D,WAAO;AAAA,EACT;AAEA,MAAI,QAAQ,SAAS,MAAM;AACzB,YAAQ,IAAI,KAAK,UAAU,QAAQ,MAAM,CAAC,CAAC;AAC3C,WAAO;AAAA,EACT;AAEA,SAAO,MAAM;AAEb,QAAM,cAAc,OAAO,QAAQ,OAAO,CAAC,WAAW,OAAO,WAAW,aAAa;AACrF,MAAI,YAAY,SAAS,GAAG;AAC1B,YAAQ,IAAI,EAAE;AACd;AAAA,MACE,GAAG,YAAY,MAAM;AAAA,IACvB;AAAA,EACF,WAAW,OAAO,QAAQ,WAAW,GAAG;AACtC,YAAQ,IAAI,EAAE;AACd,cAAU,wEAAwE;AAAA,EACpF;AAEA,SAAO;AACT;","names":[]}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
issueClosesLines,
|
|
4
|
+
multiIssueContext,
|
|
5
|
+
runPr
|
|
6
|
+
} from "./chunk-2IQ6M4ZE.js";
|
|
7
|
+
import "./chunk-PBTA3QQX.js";
|
|
8
|
+
import "./chunk-76MMPJ53.js";
|
|
9
|
+
import "./chunk-REO3PXXO.js";
|
|
10
|
+
import "./chunk-PZTWU277.js";
|
|
11
|
+
import "./chunk-KMMASOA6.js";
|
|
12
|
+
import "./chunk-PHW2YV6N.js";
|
|
13
|
+
import "./chunk-KMRFRXIT.js";
|
|
14
|
+
import "./chunk-CGKLE6WR.js";
|
|
15
|
+
import "./chunk-LEW4QM74.js";
|
|
16
|
+
import "./chunk-3BWZO4YI.js";
|
|
17
|
+
import "./chunk-OOVG2KBY.js";
|
|
18
|
+
import "./chunk-3D4A4B46.js";
|
|
19
|
+
import "./chunk-RVO7L4FX.js";
|
|
20
|
+
import "./chunk-QJ5UZAH5.js";
|
|
21
|
+
import "./chunk-QK7WUO7A.js";
|
|
22
|
+
import "./chunk-TYRNTSPJ.js";
|
|
23
|
+
import "./chunk-6QYMSNVA.js";
|
|
24
|
+
import "./chunk-3WFZETIM.js";
|
|
25
|
+
import "./chunk-HAKDLR6N.js";
|
|
26
|
+
import "./chunk-BEZ5BA5U.js";
|
|
27
|
+
import "./chunk-67YIU2VB.js";
|
|
28
|
+
import "./chunk-P3LRUNC6.js";
|
|
29
|
+
import "./chunk-G5EK2XRN.js";
|
|
30
|
+
import "./chunk-J3SOTUVI.js";
|
|
31
|
+
import "./chunk-LBGHI4S2.js";
|
|
32
|
+
import "./chunk-7Z4Y3VKM.js";
|
|
33
|
+
import "./chunk-XEYH6ZZI.js";
|
|
34
|
+
export {
|
|
35
|
+
issueClosesLines,
|
|
36
|
+
multiIssueContext,
|
|
37
|
+
runPr
|
|
38
|
+
};
|
|
39
|
+
//# sourceMappingURL=pr-OMWCGUOW.js.map
|