mandrel 2.65.0 → 2.67.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/.agents/agents/acceptance-critic.md +7 -7
- package/.agents/agents/auditor.md +17 -18
- package/.agents/agents/plan-critic.md +5 -5
- package/.agents/agents/story-worker.md +5 -5
- package/.agents/docs/agentrc-reference.json +2 -1
- package/.agents/docs/configuration.md +2 -1
- package/.agents/docs/execution-reference.md +27 -5
- package/.agents/docs/workflows.md +4 -2
- package/.agents/instructions.md +12 -13
- package/.agents/rules/ci-remediation.md +3 -3
- package/.agents/rules/gherkin-standards.md +3 -2
- package/.agents/rules/git-conventions-reference.md +17 -8
- package/.agents/rules/git-conventions.md +10 -8
- package/.agents/rules/testing-standards.md +8 -7
- package/.agents/runtime-deps.json +1 -1
- package/.agents/schemas/agentrc.schema.json +6 -1
- package/.agents/scripts/boot-sweep.js +97 -9
- package/.agents/scripts/bootstrap.js +94 -89
- package/.agents/scripts/{git-cleanup.js → clean-git.js} +2 -2
- package/.agents/scripts/clean-temp.js +54 -0
- package/.agents/scripts/clean-worktrees.js +593 -0
- package/.agents/scripts/drain-pending-cleanup.js +5 -4
- package/.agents/scripts/lib/baselines/duplication-scanner.js +17 -7
- package/.agents/scripts/lib/bootstrap/project-bootstrap.js +78 -78
- package/.agents/scripts/lib/clean-temp.js +440 -0
- package/.agents/scripts/lib/cli/standard-args.js +60 -76
- package/.agents/scripts/lib/cli-args.js +26 -0
- package/.agents/scripts/lib/config/gates/shared.js +3 -3
- package/.agents/scripts/lib/config-settings-schema-delivery.js +11 -2
- package/.agents/scripts/lib/feedback-loop/graduate-steps.js +205 -0
- package/.agents/scripts/lib/feedback-loop/graduator-core.js +47 -782
- package/.agents/scripts/lib/feedback-loop/graduator-gh.js +449 -0
- package/.agents/scripts/lib/generated/agentrc-validator.js +1 -1
- package/.agents/scripts/lib/observability/close-telemetry.js +330 -0
- package/.agents/scripts/lib/observability/runtime-friction.js +2 -0
- package/.agents/scripts/lib/observability/signal-validator.js +17 -5
- package/.agents/scripts/lib/observability/source-classifier.js +3 -1
- package/.agents/scripts/lib/orchestration/code-review.js +22 -0
- package/.agents/scripts/lib/orchestration/git-cleanup/phases/cli.js +1 -1
- package/.agents/scripts/lib/orchestration/plan-metrics.js +76 -63
- package/.agents/scripts/lib/orchestration/plan-runner/worktree-sweep.js +149 -97
- package/.agents/scripts/lib/orchestration/review-providers/review-provider-factory.js +23 -0
- package/.agents/scripts/lib/orchestration/run-epilogue.js +6 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/code-review.js +2 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/confirm-merge.js +349 -263
- package/.agents/scripts/lib/orchestration/single-story-close/phases/options.js +21 -7
- package/.agents/scripts/lib/orchestration/single-story-close/phases/review-override.js +4 -0
- package/.agents/scripts/lib/orchestration/single-story-close/runner.js +327 -314
- package/.agents/scripts/lib/orchestration/ticket-validator.js +19 -36
- package/.agents/scripts/lib/signals/detectors/common.js +63 -51
- package/.agents/scripts/lib/single-story-sweep.js +2 -2
- package/.agents/scripts/lib/temp-removal.js +110 -0
- package/.agents/scripts/lib/temp-retention.js +122 -73
- package/.agents/scripts/lib/transpile.js +28 -3
- package/.agents/scripts/lib/worktree/canonical-path.js +34 -0
- package/.agents/scripts/lib/worktree/lifecycle/reap.js +15 -4
- package/.agents/scripts/single-story-close.js +10 -2
- package/.agents/scripts/single-story-confirm-merge.js +267 -238
- package/.agents/scripts/single-story-init.js +120 -17
- package/.agents/skills/core/idea-refinement/SKILL.md +6 -6
- package/.agents/skills/stack/qa/qa-harness/SKILL.md +1 -2
- package/.agents/workflows/audit-architecture.md +5 -4
- package/.agents/workflows/audit-documentation.md +5 -5
- package/.agents/workflows/audit-performance.md +10 -10
- package/.agents/workflows/{git-cleanup.md → clean-git.md} +10 -10
- package/.agents/workflows/clean-temp.md +67 -0
- package/.agents/workflows/clean-worktrees.md +63 -0
- package/.agents/workflows/git-deliver.md +1 -1
- package/.agents/workflows/helpers/acceptance-self-eval.md +11 -10
- package/.agents/workflows/helpers/audit-lens-core.md +30 -57
- package/.agents/workflows/helpers/deliver-digest.md +2 -2
- package/.agents/workflows/helpers/deliver-reference.md +3 -1
- package/.agents/workflows/helpers/deliver-story-reference.md +2 -2
- package/.agents/workflows/helpers/deliver-story.md +6 -1
- package/.agents/workflows/helpers/parallel-tooling.md +16 -18
- package/.agents/workflows/mandrel-deliver.md +1 -1
- package/.agents/workflows/mandrel-plan.md +6 -5
- package/docs/CHANGELOG.md +39 -0
- package/lib/cli/guarded-sync.js +87 -0
- package/lib/cli/sync-agents.js +9 -92
- package/lib/cli/sync-commands.js +9 -101
- package/package.json +2 -2
|
@@ -3,13 +3,18 @@
|
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* boot-sweep.js — non-interactive *protected* merged-branch sweep over
|
|
6
|
-
* `sweepMergedBranches` (flags: see HELP). Unlike `git
|
|
6
|
+
* `sweepMergedBranches` (flags: see HELP). Unlike `clean-git --branches` it
|
|
7
7
|
* always skips a branch with unpushed work, a dirty worktree or an open
|
|
8
8
|
* parent Story. Best-effort: failures land in the envelope, exit is always 0.
|
|
9
9
|
*
|
|
10
|
+
* After the branch sweep it runs the closed-Story worktree sweep
|
|
11
|
+
* (`sweepStaleStoryWorktrees`) under the same lock: `.worktrees/story-<id>`
|
|
12
|
+
* trees whose Story is closed or `agent::done` are removed; an open Story's
|
|
13
|
+
* tree and the tree this process runs from are never touched.
|
|
14
|
+
*
|
|
10
15
|
* `content-merged` branches (merge-tree equivalence — no merge check ever
|
|
11
16
|
* validated their exact diff) are never reaped here, only reported for
|
|
12
|
-
* `/git
|
|
17
|
+
* `/clean-git`.
|
|
13
18
|
*/
|
|
14
19
|
|
|
15
20
|
import path from 'node:path';
|
|
@@ -18,9 +23,13 @@ import { parseArgs } from 'node:util';
|
|
|
18
23
|
import { runAsCli } from './lib/cli-utils.js';
|
|
19
24
|
import { PROJECT_ROOT, resolveConfig } from './lib/config-resolver.js';
|
|
20
25
|
import { Logger } from './lib/Logger.js';
|
|
26
|
+
import { sweepStaleStoryWorktrees } from './lib/orchestration/plan-runner/worktree-sweep.js';
|
|
21
27
|
import { createProvider } from './lib/provider-factory.js';
|
|
22
28
|
import { buildProtectionCtx } from './lib/single-story-sweep/protection-ctx.js';
|
|
23
|
-
import {
|
|
29
|
+
import {
|
|
30
|
+
acquireSweepLock,
|
|
31
|
+
resolveSweepLockPath,
|
|
32
|
+
} from './lib/single-story-sweep/sweep-lock.js';
|
|
24
33
|
import { sweepMergedBranches } from './lib/single-story-sweep.js';
|
|
25
34
|
import { sweepTempRetention } from './lib/temp-retention.js';
|
|
26
35
|
|
|
@@ -46,10 +55,13 @@ Runs the protected merged-branch boot sweep non-interactively: reaps every
|
|
|
46
55
|
local branch whose PR is MERGED and whose HEAD matches the merged headRefOid,
|
|
47
56
|
skipping any candidate the protection partition flags (unpushed work, dirty
|
|
48
57
|
worktree, still-open parent Story), then fast-forwards the base branch.
|
|
58
|
+
Then removes every .worktrees/story-<id> tree whose Story is closed or
|
|
59
|
+
agent::done (never an open Story's, never the tree this process runs from);
|
|
60
|
+
the outcome lands under "worktreeSweep".
|
|
49
61
|
Branches detected only via the weaker content-equivalence signal
|
|
50
62
|
(detectedBy: 'content-merged') are never reaped here — they are reported
|
|
51
63
|
under "contentMerged" (and a routing hint in the summary line) for the
|
|
52
|
-
operator to send to /git
|
|
64
|
+
operator to send to /clean-git.
|
|
53
65
|
|
|
54
66
|
Options:
|
|
55
67
|
--include <glob> Branch glob to sweep (repeatable). Default: story-*
|
|
@@ -60,6 +72,63 @@ Options:
|
|
|
60
72
|
--json Emit the result envelope as JSON.
|
|
61
73
|
`;
|
|
62
74
|
|
|
75
|
+
/**
|
|
76
|
+
* The closed-Story worktree sweep under the shared sweep lock; never throws.
|
|
77
|
+
* A contended lock skips it — the holder's next boot picks the trees up.
|
|
78
|
+
* Shared with `single-story-init.js`, whose boot never reaches
|
|
79
|
+
* {@link runBootSweep}: one sweep, one lock, two callers. `keepPaths` joins
|
|
80
|
+
* the sweep's running-tree guard — init passes the tree it is about to
|
|
81
|
+
* work in, so the Story being initialized is never removed.
|
|
82
|
+
*
|
|
83
|
+
* @param {{
|
|
84
|
+
* root: string,
|
|
85
|
+
* provider: object,
|
|
86
|
+
* lockPath: string,
|
|
87
|
+
* lockTimeoutMs: number,
|
|
88
|
+
* sweepFn?: Function,
|
|
89
|
+
* acquireLockFn?: Function,
|
|
90
|
+
* logger: object,
|
|
91
|
+
* logTag?: string,
|
|
92
|
+
* keepPaths?: string[],
|
|
93
|
+
* }} args
|
|
94
|
+
* @returns {Promise<object>} `{ ok, reaped, skipped, reason?, error? }`.
|
|
95
|
+
*/
|
|
96
|
+
export async function runWorktreeSweep({
|
|
97
|
+
root,
|
|
98
|
+
provider,
|
|
99
|
+
lockPath,
|
|
100
|
+
lockTimeoutMs,
|
|
101
|
+
sweepFn = sweepStaleStoryWorktrees,
|
|
102
|
+
acquireLockFn = acquireSweepLock,
|
|
103
|
+
logger,
|
|
104
|
+
logTag = '[boot-sweep]',
|
|
105
|
+
keepPaths = [],
|
|
106
|
+
}) {
|
|
107
|
+
const lock = acquireLockFn({ lockPath, timeoutMs: lockTimeoutMs });
|
|
108
|
+
if (!lock.acquired) {
|
|
109
|
+
return { ok: true, reason: `lock-${lock.reason}`, reaped: [], skipped: [] };
|
|
110
|
+
}
|
|
111
|
+
try {
|
|
112
|
+
const result = await sweepFn({
|
|
113
|
+
provider,
|
|
114
|
+
repoRoot: root,
|
|
115
|
+
runningPaths: keepPaths,
|
|
116
|
+
logger: {
|
|
117
|
+
info: (m) => logger.info?.(`${logTag} ${m}`),
|
|
118
|
+
warn: (m) => logger.warn?.(`${logTag} ${m}`),
|
|
119
|
+
error: (m) => logger.warn?.(`${logTag} ${m}`),
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
return { ok: true, ...result };
|
|
123
|
+
} catch (err) {
|
|
124
|
+
const msg = err?.message ?? String(err);
|
|
125
|
+
logger.warn?.(`${logTag} worktree sweep threw (host continues): ${msg}`);
|
|
126
|
+
return { ok: false, error: msg, reaped: [], skipped: [] };
|
|
127
|
+
} finally {
|
|
128
|
+
lock.release();
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
63
132
|
/**
|
|
64
133
|
* Run the protected boot sweep; never throws.
|
|
65
134
|
*
|
|
@@ -73,11 +142,13 @@ Options:
|
|
|
73
142
|
* injectedConfig?: object,
|
|
74
143
|
* injectedProvider?: object,
|
|
75
144
|
* injectedSweep?: Function,
|
|
145
|
+
* worktreeSweepFn?: Function,
|
|
146
|
+
* acquireLockFn?: Function,
|
|
76
147
|
* purgeFn?: Function,
|
|
77
148
|
* logger?: { info?: Function, warn?: Function },
|
|
78
149
|
* }} [args]
|
|
79
150
|
* @returns {Promise<object>} the {@link sweepMergedBranches} envelope plus
|
|
80
|
-
* `tempPurge`.
|
|
151
|
+
* `worktreeSweep` and `tempPurge`.
|
|
81
152
|
*/
|
|
82
153
|
export async function runBootSweep({
|
|
83
154
|
cwd,
|
|
@@ -89,6 +160,8 @@ export async function runBootSweep({
|
|
|
89
160
|
injectedConfig,
|
|
90
161
|
injectedProvider,
|
|
91
162
|
injectedSweep,
|
|
163
|
+
worktreeSweepFn = sweepStaleStoryWorktrees,
|
|
164
|
+
acquireLockFn = acquireSweepLock,
|
|
92
165
|
purgeFn = sweepTempRetention,
|
|
93
166
|
logger = Logger,
|
|
94
167
|
} = {}) {
|
|
@@ -130,6 +203,16 @@ export async function runBootSweep({
|
|
|
130
203
|
lockTimeoutMs,
|
|
131
204
|
});
|
|
132
205
|
|
|
206
|
+
const worktreeSweep = await runWorktreeSweep({
|
|
207
|
+
root,
|
|
208
|
+
provider,
|
|
209
|
+
lockPath,
|
|
210
|
+
lockTimeoutMs,
|
|
211
|
+
sweepFn: worktreeSweepFn,
|
|
212
|
+
acquireLockFn,
|
|
213
|
+
logger,
|
|
214
|
+
});
|
|
215
|
+
|
|
133
216
|
// Temp-retention catch-up: reaped branches are confirmed merges, so their
|
|
134
217
|
// artifacts are spent; the age floor collects the rest.
|
|
135
218
|
const purge = await purgeFn({
|
|
@@ -138,7 +221,7 @@ export async function runBootSweep({
|
|
|
138
221
|
label: 'boot-sweep',
|
|
139
222
|
logger,
|
|
140
223
|
});
|
|
141
|
-
return { ...result, tempPurge: purge };
|
|
224
|
+
return { ...result, worktreeSweep, tempPurge: purge };
|
|
142
225
|
} catch (err) {
|
|
143
226
|
const msg = err?.message ?? String(err);
|
|
144
227
|
logger.warn?.(`[boot-sweep] sweep threw (host continues): ${msg}`);
|
|
@@ -157,7 +240,7 @@ export async function runBootSweep({
|
|
|
157
240
|
}
|
|
158
241
|
|
|
159
242
|
/**
|
|
160
|
-
* One-line summary; a nonzero `contentMerged` count adds a `/git
|
|
243
|
+
* One-line summary; a nonzero `contentMerged` count adds a `/clean-git` hint.
|
|
161
244
|
*
|
|
162
245
|
* @param {{ localDeleted: number, remoteDeleted: number, protected?: Array, contentMerged?: Array }} result
|
|
163
246
|
* @returns {string}
|
|
@@ -165,11 +248,16 @@ export async function runBootSweep({
|
|
|
165
248
|
export function buildSummaryLine(result) {
|
|
166
249
|
const protectedCount = result.protected?.length ?? 0;
|
|
167
250
|
const contentMergedCount = result.contentMerged?.length ?? 0;
|
|
251
|
+
const worktreesReaped = result.worktreeSweep?.reaped?.length ?? 0;
|
|
252
|
+
const worktreeSuffix =
|
|
253
|
+
worktreesReaped > 0
|
|
254
|
+
? `; removed ${worktreesReaped} closed-Story worktree(s)`
|
|
255
|
+
: '';
|
|
168
256
|
const contentMergedSuffix =
|
|
169
257
|
contentMergedCount > 0
|
|
170
|
-
? `; ${contentMergedCount} content-merged branch(es) left for /git
|
|
258
|
+
? `; ${contentMergedCount} content-merged branch(es) left for /clean-git`
|
|
171
259
|
: '';
|
|
172
|
-
return `[boot-sweep] reaped ${result.localDeleted} local + ${result.remoteDeleted} remote; protected ${protectedCount}${contentMergedSuffix}.`;
|
|
260
|
+
return `[boot-sweep] reaped ${result.localDeleted} local + ${result.remoteDeleted} remote; protected ${protectedCount}${worktreeSuffix}${contentMergedSuffix}.`;
|
|
173
261
|
}
|
|
174
262
|
|
|
175
263
|
/**
|
|
@@ -716,6 +716,13 @@ async function detectCreation(answers, skipGithub) {
|
|
|
716
716
|
return creation;
|
|
717
717
|
}
|
|
718
718
|
|
|
719
|
+
function projectNameFor(pn, projects) {
|
|
720
|
+
const match = projects.find((p) => p.value === pn);
|
|
721
|
+
if (!match) return '(unknown)';
|
|
722
|
+
const m = /^(.*)\s+\(#\d+\)$/.exec(match.label);
|
|
723
|
+
return m ? m[1] : match.label;
|
|
724
|
+
}
|
|
725
|
+
|
|
719
726
|
/**
|
|
720
727
|
* `{ name, number }` for the summary; the picker stores only the number, so
|
|
721
728
|
* an existing project's name is looked up.
|
|
@@ -723,20 +730,78 @@ async function detectCreation(answers, skipGithub) {
|
|
|
723
730
|
function resolveProjectDisplay(answers, skipGithub, projectsList) {
|
|
724
731
|
const pn = answers.projectNumber;
|
|
725
732
|
if (!pn) return { name: '(skip)', number: '(skip)' };
|
|
726
|
-
if (
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
733
|
+
if (!/^\d+$/.test(pn)) return { name: pn, number: '(new)' };
|
|
734
|
+
if (skipGithub) return { name: '(unknown)', number: pn };
|
|
735
|
+
const projects =
|
|
736
|
+
projectsList ?? safeList(() => listProjects({ owner: answers.owner }));
|
|
737
|
+
return { name: projectNameFor(pn, projects), number: pn };
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
/** Opt-ins default off; dry-run resolves them without prompting. */
|
|
741
|
+
const OPT_INS = Object.freeze([
|
|
742
|
+
{
|
|
743
|
+
key: 'withProjectBoard',
|
|
744
|
+
flag: 'with-project-board',
|
|
745
|
+
prompt: 'Set up project board fields (Status, custom)?',
|
|
746
|
+
},
|
|
747
|
+
{
|
|
748
|
+
key: 'withIssueForms',
|
|
749
|
+
flag: 'with-issue-forms',
|
|
750
|
+
prompt: 'Generate GitHub Issue Form templates?',
|
|
751
|
+
},
|
|
752
|
+
{
|
|
753
|
+
key: 'withQuality',
|
|
754
|
+
flag: 'with-quality',
|
|
755
|
+
prompt:
|
|
756
|
+
'Install local quality gates (pre-commit hook + quality:preview/watch scripts)?',
|
|
757
|
+
},
|
|
758
|
+
]);
|
|
759
|
+
|
|
760
|
+
async function resolveOptIns(state) {
|
|
761
|
+
const optIns = {};
|
|
762
|
+
for (const { key, flag, prompt } of OPT_INS) {
|
|
763
|
+
let on = Boolean(state.flags[flag]);
|
|
764
|
+
if (!state.flags['dry-run'] && !on) {
|
|
765
|
+
on = await confirmYesNo(prompt, state.interactive, false);
|
|
736
766
|
}
|
|
737
|
-
|
|
767
|
+
optIns[key] = on;
|
|
738
768
|
}
|
|
739
|
-
return
|
|
769
|
+
return optIns;
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
async function approveCreation(state, creation) {
|
|
773
|
+
if (state.flags['dry-run'] || !(creation.newRepo || creation.newProject)) {
|
|
774
|
+
return true;
|
|
775
|
+
}
|
|
776
|
+
return confirmYesNo(
|
|
777
|
+
'Create the new GitHub repo/project listed above?',
|
|
778
|
+
state.interactive,
|
|
779
|
+
);
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
async function confirmSummary(state, answers, creation, projectsList) {
|
|
783
|
+
const skipGithub = Boolean(state.flags['skip-github']);
|
|
784
|
+
const project = resolveProjectDisplay(answers, skipGithub, projectsList);
|
|
785
|
+
Logger.info(
|
|
786
|
+
renderAnswerSummary(
|
|
787
|
+
answers,
|
|
788
|
+
creation,
|
|
789
|
+
project,
|
|
790
|
+
state.gitInitialized,
|
|
791
|
+
resolveRepoVisibility(state.flags),
|
|
792
|
+
),
|
|
793
|
+
);
|
|
794
|
+
return confirmYesNo('Is this correct?', state.interactive);
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
/** Owner repo/project lists, fetched once for pickers and summary. */
|
|
798
|
+
function fetchPickerLists(state, skipGithub) {
|
|
799
|
+
const owner = resolveOwnerForPicker(state.defaults, state.flags);
|
|
800
|
+
if (skipGithub || !owner) return { reposList: [], projectsList: [] };
|
|
801
|
+
return {
|
|
802
|
+
reposList: safeList(() => listRepos({ owner }).map(bareRepoName)),
|
|
803
|
+
projectsList: safeList(() => listProjects({ owner })),
|
|
804
|
+
};
|
|
740
805
|
}
|
|
741
806
|
|
|
742
807
|
/**
|
|
@@ -745,22 +810,16 @@ function resolveProjectDisplay(answers, skipGithub, projectsList) {
|
|
|
745
810
|
*/
|
|
746
811
|
export async function collectAndConfirm(state) {
|
|
747
812
|
const skipGithub = Boolean(state.flags['skip-github']);
|
|
748
|
-
const
|
|
749
|
-
// Fetched once for pickers and summary, so the name never needs a second call.
|
|
750
|
-
const reposList =
|
|
751
|
-
!skipGithub && owner
|
|
752
|
-
? safeList(() => listRepos({ owner }).map(bareRepoName))
|
|
753
|
-
: [];
|
|
754
|
-
const projectsList =
|
|
755
|
-
!skipGithub && owner ? safeList(() => listProjects({ owner })) : [];
|
|
756
|
-
|
|
813
|
+
const lists = fetchPickerLists(state, skipGithub);
|
|
757
814
|
let silentAccept = state.silentAccept;
|
|
758
815
|
for (;;) {
|
|
759
816
|
const { answers, missing } = await collectAnswers({
|
|
760
|
-
questions: buildQuestions(
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
817
|
+
questions: buildQuestions(
|
|
818
|
+
state.defaults,
|
|
819
|
+
state.flags,
|
|
820
|
+
process.env,
|
|
821
|
+
lists,
|
|
822
|
+
),
|
|
764
823
|
flags: state.flags,
|
|
765
824
|
interactive: state.interactive,
|
|
766
825
|
assumeYes: state.assumeYes,
|
|
@@ -775,78 +834,24 @@ export async function collectAndConfirm(state) {
|
|
|
775
834
|
);
|
|
776
835
|
return { ok: false, exit: 1 };
|
|
777
836
|
}
|
|
778
|
-
|
|
779
|
-
|
|
837
|
+
answers.operatorHandle = normalizeHandleAnswer(
|
|
838
|
+
answers.operatorHandle || answers.owner,
|
|
839
|
+
);
|
|
780
840
|
|
|
781
841
|
const creation = await detectCreation(answers, skipGithub);
|
|
782
|
-
|
|
783
|
-
Logger.info(
|
|
784
|
-
renderAnswerSummary(
|
|
785
|
-
answers,
|
|
786
|
-
creation,
|
|
787
|
-
project,
|
|
788
|
-
state.gitInitialized,
|
|
789
|
-
resolveRepoVisibility(state.flags),
|
|
790
|
-
),
|
|
791
|
-
);
|
|
792
|
-
const correct = await confirmYesNo('Is this correct?', state.interactive);
|
|
793
|
-
if (!correct) {
|
|
842
|
+
if (!(await confirmSummary(state, answers, creation, lists.projectsList))) {
|
|
794
843
|
Logger.info('[Bootstrap] Okay — let’s try again.');
|
|
795
844
|
silentAccept = [];
|
|
796
845
|
continue;
|
|
797
846
|
}
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
'Create the new GitHub repo/project listed above?',
|
|
802
|
-
state.interactive,
|
|
803
|
-
);
|
|
804
|
-
if (!approved) {
|
|
805
|
-
Logger.error(
|
|
806
|
-
'[Bootstrap] Creation declined — cannot continue without the repo/project. Exiting.',
|
|
807
|
-
);
|
|
808
|
-
return { ok: false, exit: 1 };
|
|
809
|
-
}
|
|
810
|
-
}
|
|
811
|
-
|
|
812
|
-
// Opt-ins default off; dry-run resolves them without prompting.
|
|
813
|
-
let withProjectBoard = Boolean(state.flags['with-project-board']);
|
|
814
|
-
if (!state.flags['dry-run'] && !withProjectBoard) {
|
|
815
|
-
withProjectBoard = await confirmYesNo(
|
|
816
|
-
'Set up project board fields (Status, custom)?',
|
|
817
|
-
state.interactive,
|
|
818
|
-
false,
|
|
819
|
-
);
|
|
820
|
-
}
|
|
821
|
-
|
|
822
|
-
let withIssueForms = Boolean(state.flags['with-issue-forms']);
|
|
823
|
-
if (!state.flags['dry-run'] && !withIssueForms) {
|
|
824
|
-
withIssueForms = await confirmYesNo(
|
|
825
|
-
'Generate GitHub Issue Form templates?',
|
|
826
|
-
state.interactive,
|
|
827
|
-
false,
|
|
828
|
-
);
|
|
829
|
-
}
|
|
830
|
-
|
|
831
|
-
let withQuality = Boolean(state.flags['with-quality']);
|
|
832
|
-
if (!state.flags['dry-run'] && !withQuality) {
|
|
833
|
-
withQuality = await confirmYesNo(
|
|
834
|
-
'Install local quality gates (pre-commit hook + quality:preview/watch scripts)?',
|
|
835
|
-
state.interactive,
|
|
836
|
-
false,
|
|
847
|
+
if (!(await approveCreation(state, creation))) {
|
|
848
|
+
Logger.error(
|
|
849
|
+
'[Bootstrap] Creation declined — cannot continue without the repo/project. Exiting.',
|
|
837
850
|
);
|
|
851
|
+
return { ok: false, exit: 1 };
|
|
838
852
|
}
|
|
839
|
-
|
|
840
|
-
return {
|
|
841
|
-
ok: true,
|
|
842
|
-
payload: {
|
|
843
|
-
answers,
|
|
844
|
-
creation,
|
|
845
|
-
withProjectBoard,
|
|
846
|
-
withIssueForms,
|
|
847
|
-
withQuality,
|
|
848
|
-
},
|
|
849
|
-
};
|
|
853
|
+
const optIns = await resolveOptIns(state);
|
|
854
|
+
return { ok: true, payload: { answers, creation, ...optIns } };
|
|
850
855
|
}
|
|
851
856
|
}
|
|
852
857
|
|
|
@@ -100,10 +100,10 @@ async function main() {
|
|
|
100
100
|
}
|
|
101
101
|
|
|
102
102
|
runAsCli(import.meta.url, main, {
|
|
103
|
-
source: 'git
|
|
103
|
+
source: 'clean-git',
|
|
104
104
|
usage: {
|
|
105
105
|
invocation:
|
|
106
|
-
'node .agents/scripts/git
|
|
106
|
+
'node .agents/scripts/clean-git.js [--execute] [--yes] [--json] [phase flags] [filters]',
|
|
107
107
|
summary:
|
|
108
108
|
'Tidy the local checkout in four phases — fast-forward the base branch, prune stale remote refs, reap merged branches, triage stashes. Dry-run unless --execute.',
|
|
109
109
|
flags: [
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/* node:coverage ignore file -- thin CLI shell; the engine is lib/clean-temp.js */
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* `/clean-temp` — preview (default) or delete spent entries under the
|
|
6
|
+
* project's tempRoot. Thin shell over `lib/clean-temp.js`.
|
|
7
|
+
*
|
|
8
|
+
* Exit codes: 0 clean or dry-run, 1 refused (tempRoot outside the project
|
|
9
|
+
* root) or a deletion failed.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { runCleanTemp } from './lib/clean-temp.js';
|
|
13
|
+
import { runAsCli } from './lib/cli-utils.js';
|
|
14
|
+
import { resolveConfig } from './lib/config-resolver.js';
|
|
15
|
+
import { promptYesNo } from './lib/orchestration/git-cleanup/phases/prompts.js';
|
|
16
|
+
import { createProvider } from './lib/provider-factory.js';
|
|
17
|
+
|
|
18
|
+
/* node:coverage ignore next */
|
|
19
|
+
async function main() {
|
|
20
|
+
const { exitCode } = await runCleanTemp({
|
|
21
|
+
argv: process.argv.slice(2),
|
|
22
|
+
cwd: process.cwd(),
|
|
23
|
+
loadConfig: (projectRoot) => resolveConfig({ cwd: projectRoot }),
|
|
24
|
+
getProvider: createProvider,
|
|
25
|
+
confirm: promptYesNo,
|
|
26
|
+
write: (text) => process.stdout.write(text),
|
|
27
|
+
writeErr: (text) => process.stderr.write(text),
|
|
28
|
+
});
|
|
29
|
+
return exitCode;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
runAsCli(import.meta.url, main, {
|
|
33
|
+
source: 'clean-temp',
|
|
34
|
+
propagateExitCode: true,
|
|
35
|
+
usage: {
|
|
36
|
+
invocation:
|
|
37
|
+
'node .agents/scripts/clean-temp.js [--execute] [--yes] [--json] [--cwd <path>]',
|
|
38
|
+
summary:
|
|
39
|
+
"Sort every top-level entry under the project's tempRoot into framework / closed-issue / aged / kept buckets. Dry-run unless --execute.",
|
|
40
|
+
flags: [
|
|
41
|
+
['--execute', 'Delete the confirmed buckets (default is a dry run).'],
|
|
42
|
+
[
|
|
43
|
+
'--yes',
|
|
44
|
+
'Skip the per-bucket prompts; deletes framework and closed-issue only — aged is never deleted unattended.',
|
|
45
|
+
],
|
|
46
|
+
['--json', 'Emit the result envelope as JSON.'],
|
|
47
|
+
['--cwd <path>', 'Project directory (default: process cwd).'],
|
|
48
|
+
],
|
|
49
|
+
notes: [
|
|
50
|
+
'Refuses (exit 1) when the resolved tempRoot is not inside the project root.',
|
|
51
|
+
'qa/, cache/, *.lock and signals.ndjson (at any depth) are never deleted.',
|
|
52
|
+
],
|
|
53
|
+
},
|
|
54
|
+
});
|