@gobing-ai/spur 0.4.8 → 0.4.9
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/.claude-plugin/marketplace.json +1 -1
- package/config/pipeline-budgets.json +2 -2
- package/config/rules/boundary/config-loading-ownership.yaml +1 -0
- package/config/rules/boundary/sp-plugin-standalone.yaml +1 -1
- package/config/rules/boundary/sp-script-placement.yaml +1 -1
- package/config/rules/docs/sourced-api-claims.yaml +45 -7
- package/config/rules/strict/runtime-boundaries.yaml +40 -1
- package/config/rules/structure/protected-files.yaml +1 -1
- package/config/rules/surface/check-cli-surface.yaml +33 -0
- package/config/rules/typescript/no-debugger.yaml +3 -0
- package/config/workflow-candidates.json +2 -2
- package/config/workflows/feature-verification.yaml +6 -11
- package/config/workflows/history-anatomy.yaml +14 -28
- package/config/workflows/idea-pipeline.yaml +6 -19
- package/config/workflows/pr-review.yaml +9 -16
- package/config/workflows/task-pipeline.yaml +16 -23
- package/config/workflows/wrapup-pipeline.yaml +21 -63
- package/package.json +3 -3
- package/plugins/sp/README.md +70 -37
- package/plugins/sp/agents/super-planner.md +5 -5
- package/plugins/sp/agents/super-reviewer.md +2 -2
- package/plugins/sp/commands/dev-changelog.md +1 -1
- package/plugins/sp/commands/dev-daily.md +1 -1
- package/plugins/sp/commands/dev-pr-review.md +1 -1
- package/plugins/sp/commands/dev-review-session.md +1 -1
- package/plugins/sp/commands/dev-runall.md +4 -4
- package/plugins/sp/commands/dev-verify.md +4 -6
- package/plugins/sp/commands/dev-verifyall.md +4 -5
- package/plugins/sp/hooks/hooks.json +11 -1
- package/plugins/sp/lib/context-post-tool.ts +1 -1
- package/plugins/sp/lib/context-session-start.ts +1 -1
- package/plugins/sp/lib/context-session-stop.ts +1 -1
- package/plugins/sp/lib/destructive-policy.ts +65 -1
- package/plugins/sp/plugin.json +2 -5
- package/plugins/sp/{hooks → scripts/hooks}/agent-lifecycle.ts +1 -1
- package/plugins/sp/scripts/hooks/careful-guard.ts +94 -0
- package/plugins/sp/{hooks → scripts/hooks}/context-post-tool.ts +2 -2
- package/plugins/sp/{hooks → scripts/hooks}/context-session-start.ts +1 -1
- package/plugins/sp/{hooks → scripts/hooks}/context-session-stop.ts +1 -1
- package/plugins/sp/{hooks → scripts/hooks}/fleet-guest-stop.ts +6 -4
- package/plugins/sp/{hooks → scripts/hooks}/task-write-guard.ts +38 -21
- package/plugins/sp/skills/code-implementation/SKILL.md +1 -1
- package/plugins/sp/skills/code-verification/SKILL.md +8 -8
- package/plugins/sp/skills/code-verification/references/secu-review.md +4 -3
- package/plugins/sp/skills/code-verification/references/verdict-schema.md +9 -1
- package/plugins/sp/skills/daily-summary/SKILL.md +4 -5
- package/plugins/sp/skills/dogfood-testing/SKILL.md +9 -9
- package/plugins/sp/skills/dogfood-testing/references/report-template.md +1 -1
- package/plugins/sp/skills/history-anatomy/references/modes.md +2 -2
- package/plugins/sp/skills/next-router/references/routing-table.md +1 -1
- package/plugins/sp/skills/parallel-execution/SKILL.md +1 -1
- package/plugins/sp/skills/pr-reviewing/SKILL.md +14 -15
- package/plugins/sp/skills/session-review/SKILL.md +3 -3
- package/plugins/sp/skills/source-driven-development/SKILL.md +7 -4
- package/plugins/sp/skills/spur-check/SKILL.md +14 -13
- package/plugins/sp/skills/spur-cli/SKILL.md +1 -1
- package/plugins/sp/skills/spur-cli/references/agent.md +12 -0
- package/plugins/sp/skills/spur-cli/references/builder.md +27 -6
- package/plugins/sp/skills/spur-cli/references/features.md +6 -1
- package/plugins/sp/skills/spur-cli/references/history.md +3 -0
- package/plugins/sp/skills/spur-cli/references/self.md +5 -3
- package/plugins/sp/skills/spur-cli/references/tasks/verbs.md +3 -0
- package/plugins/sp/skills/spur-cli/references/tasks.md +13 -3
- package/plugins/sp/skills/spur-cli/references/workflows.md +36 -1
- package/plugins/sp/skills/spur-dev/SKILL.md +1 -1
- package/plugins/sp/skills/spur-dev/references/cross-cutting.md +1 -1
- package/plugins/sp/skills/spur-dev/references/dev-operations.md +12 -12
- package/plugins/sp/skills/spur-dev/references/execution-batch-report.md +10 -11
- package/plugins/sp/skills/spur-dev/references/execution-batch.md +16 -17
- package/plugins/sp/skills/spur-dev/references/execution-parallel-isolation.md +1 -1
- package/plugins/sp/skills/spur-dev/references/execution-worktree-landing.md +8 -11
- package/plugins/sp/skills/spur-dev/references/execution-worktree-setup.md +6 -8
- package/plugins/sp/skills/spur-dev/references/gate-checklists.md +1 -1
- package/plugins/sp/skills/spur-dev/references/inline-pipeline-driver.md +33 -40
- package/plugins/sp/skills/spur-dev/references/structured-trace-emission.md +24 -24
- package/plugins/sp/skills/spur-doctor/SKILL.md +5 -4
- package/spur.js +41901 -30598
- package/config/plugin-scripts.json +0 -116
- package/config/script-placement-baseline.json +0 -33
- package/plugins/sp/hooks/careful-guard.ts +0 -74
- package/plugins/sp/hooks/pi/guard-extension.ts +0 -304
- package/plugins/sp/lib/dogfood-pipeline.ts +0 -265
- package/plugins/sp/lib/dogfood-report.ts +0 -278
- package/plugins/sp/lib/history-anatomy.generated.d.mts +0 -112
- package/plugins/sp/lib/history-anatomy.generated.mjs +0 -691
- package/plugins/sp/lib/idea-handoff.generated.d.mts +0 -17
- package/plugins/sp/lib/idea-handoff.generated.mjs +0 -1446
- package/plugins/sp/lib/inline-run-app.ts +0 -68
- package/plugins/sp/lib/inline-run.generated.d.mts +0 -37
- package/plugins/sp/lib/inline-run.generated.mjs +0 -3103
- package/plugins/sp/lib/plan-projection.generated.d.mts +0 -197
- package/plugins/sp/lib/plan-projection.generated.mjs +0 -982
- package/plugins/sp/lib/quality-gate.generated.d.mts +0 -139
- package/plugins/sp/lib/quality-gate.generated.mjs +0 -30510
- package/plugins/sp/lib/residual-scan.generated.d.mts +0 -93
- package/plugins/sp/lib/residual-scan.generated.mjs +0 -383
- package/plugins/sp/lib/run-summary-core.ts +0 -225
- package/plugins/sp/lib/spur-bin.ts +0 -36
- package/plugins/sp/lib/step-profile.generated.d.mts +0 -71
- package/plugins/sp/lib/step-profile.generated.mjs +0 -174
- package/plugins/sp/lib/transcript.ts +0 -112
- package/plugins/sp/scripts/batch-plan.mjs +0 -1096
- package/plugins/sp/scripts/batch-plan.ts +0 -145
- package/plugins/sp/scripts/batch-preflight.mjs +0 -423
- package/plugins/sp/scripts/batch-preflight.ts +0 -270
- package/plugins/sp/scripts/commit-guard.mjs +0 -226
- package/plugins/sp/scripts/commit-guard.ts +0 -248
- package/plugins/sp/scripts/daily-summary/daily-summary.mjs +0 -626
- package/plugins/sp/scripts/daily-summary/daily-summary.ts +0 -855
- package/plugins/sp/scripts/dogfood-testing/detect-pipeline-driving.mjs +0 -211
- package/plugins/sp/scripts/dogfood-testing/detect-pipeline-driving.ts +0 -110
- package/plugins/sp/scripts/dogfood-testing/validate-report.mjs +0 -282
- package/plugins/sp/scripts/dogfood-testing/validate-report.ts +0 -98
- package/plugins/sp/scripts/feature-verification-steps.mjs +0 -82
- package/plugins/sp/scripts/feature-verification-steps.ts +0 -164
- package/plugins/sp/scripts/history-anatomy-cache.mjs +0 -931
- package/plugins/sp/scripts/history-anatomy-cache.ts +0 -247
- package/plugins/sp/scripts/idea-handoff.mjs +0 -32
- package/plugins/sp/scripts/idea-handoff.ts +0 -47
- package/plugins/sp/scripts/inline-run-dispatch.mjs +0 -131
- package/plugins/sp/scripts/inline-run-dispatch.ts +0 -115
- package/plugins/sp/scripts/inline-run-setup.mjs +0 -746
- package/plugins/sp/scripts/inline-run-setup.ts +0 -198
- package/plugins/sp/scripts/persist-out-check.mjs +0 -217
- package/plugins/sp/scripts/persist-out-check.ts +0 -249
- package/plugins/sp/scripts/pr-reviewing.mjs +0 -712
- package/plugins/sp/scripts/pr-reviewing.ts +0 -952
- package/plugins/sp/scripts/quality-gate.mjs +0 -30491
- package/plugins/sp/scripts/quality-gate.ts +0 -80
- package/plugins/sp/scripts/residual-scan.mjs +0 -634
- package/plugins/sp/scripts/residual-scan.ts +0 -248
- package/plugins/sp/scripts/run-summary.mjs +0 -758
- package/plugins/sp/scripts/run-summary.ts +0 -107
- package/plugins/sp/scripts/script-root.mjs +0 -129
- package/plugins/sp/scripts/script-root.ts +0 -199
- package/plugins/sp/scripts/session-timeline.mjs +0 -359
- package/plugins/sp/scripts/session-timeline.ts +0 -190
- package/plugins/sp/scripts/task-diffstat.mjs +0 -181
- package/plugins/sp/scripts/task-diffstat.ts +0 -219
- package/plugins/sp/scripts/workflow-step-profile.mjs +0 -338
- package/plugins/sp/scripts/workflow-step-profile.ts +0 -165
- package/plugins/sp/scripts/wrapup-drift-probe.mjs +0 -190
- package/plugins/sp/scripts/wrapup-drift-probe.ts +0 -261
- package/plugins/sp/scripts/wrapup-steps.mjs +0 -627
- package/plugins/sp/scripts/wrapup-steps.ts +0 -891
|
@@ -1,145 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bun
|
|
2
|
-
/**
|
|
3
|
-
* batch-plan — letters-first batch plan surface (task 1104, feature I13).
|
|
4
|
-
*
|
|
5
|
-
* The projection logic lives in packages/app/src/workflow/plan-projection.ts (unit-tested
|
|
6
|
-
* there); this script is ADR-130 glue — argv/IO only — over the bundled core in
|
|
7
|
-
* ../lib/plan-projection.generated.* (regenerate: `bun run build:plugin-lib && bun run build:scripts`).
|
|
8
|
-
*
|
|
9
|
-
* Modes:
|
|
10
|
-
* waves --tasks <file> file: JSON array of {wbs, name}; prints `{ waves: [...] }` — waves
|
|
11
|
-
* of 24 tasks as A Prepare batch + A1..A4 + B..Y task rows + Z Batch report.
|
|
12
|
-
* order --tasks <file> [--concurrency <n>]
|
|
13
|
-
* file: JSON array of {wbs, dependencies?, files?}; prints the scheduleBatch
|
|
14
|
-
* result `{ order, waves, cycle, overlaps, mode, reason }` (dev-runall §2.4
|
|
15
|
-
* order + the --mode auto recommendation). A cycle is data, not an error.
|
|
16
|
-
* task-children --letter <A-Z> --plan <file>
|
|
17
|
-
* file: a `workflow show --format todo --json` payload (or a bare plan
|
|
18
|
-
* array); prints the task's phase digits as a JSON array.
|
|
19
|
-
* Exit 0 on success, 1 on unreadable/invalid input, 2 on usage error. Runs under bare
|
|
20
|
-
* `node batch-plan.mjs` (committed twin) — keep the twin in lockstep.
|
|
21
|
-
*/
|
|
22
|
-
import { readFileSync } from 'node:fs';
|
|
23
|
-
import type { BatchTask, PhasedPlanItem, ScheduleTask } from '../lib/plan-projection.generated.mjs';
|
|
24
|
-
import { buildBatchPlan, scheduleBatch, taskPhaseChildren } from '../lib/plan-projection.generated.mjs';
|
|
25
|
-
|
|
26
|
-
export * from '../lib/plan-projection.generated.mjs';
|
|
27
|
-
|
|
28
|
-
export const BATCH_PLAN_USAGE =
|
|
29
|
-
'usage: batch-plan.ts waves --tasks <tasks.json> | order --tasks <tasks.json> [--concurrency <n>] | task-children --letter <A-Z> --plan <plan.json>';
|
|
30
|
-
|
|
31
|
-
function readJsonFile(path: string): unknown {
|
|
32
|
-
return JSON.parse(readFileSync(path, 'utf8'));
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function isBatchTask(value: unknown): value is BatchTask {
|
|
36
|
-
return (
|
|
37
|
-
typeof value === 'object' &&
|
|
38
|
-
value !== null &&
|
|
39
|
-
typeof (value as BatchTask).wbs === 'string' &&
|
|
40
|
-
typeof (value as BatchTask).name === 'string'
|
|
41
|
-
);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function isStringArray(value: unknown): value is string[] {
|
|
45
|
-
return Array.isArray(value) && value.every((v) => typeof v === 'string');
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function isScheduleTask(value: unknown): value is ScheduleTask {
|
|
49
|
-
if (typeof value !== 'object' || value === null) return false;
|
|
50
|
-
const t = value as ScheduleTask;
|
|
51
|
-
return (
|
|
52
|
-
typeof t.wbs === 'string' &&
|
|
53
|
-
(t.dependencies === undefined || isStringArray(t.dependencies)) &&
|
|
54
|
-
(t.files === undefined || isStringArray(t.files))
|
|
55
|
-
);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export function main(argv: string[]): number {
|
|
59
|
-
const mode = argv[0];
|
|
60
|
-
if (mode === 'waves') {
|
|
61
|
-
if (argv[1] !== '--tasks' || argv[2] === undefined) {
|
|
62
|
-
process.stderr.write(`${BATCH_PLAN_USAGE}\n`);
|
|
63
|
-
return 2;
|
|
64
|
-
}
|
|
65
|
-
let tasks: unknown;
|
|
66
|
-
try {
|
|
67
|
-
tasks = readJsonFile(argv[2]);
|
|
68
|
-
} catch (error) {
|
|
69
|
-
process.stderr.write(
|
|
70
|
-
`batch-plan: cannot read ${argv[2]} — ${error instanceof Error ? error.message : String(error)}\n`,
|
|
71
|
-
);
|
|
72
|
-
return 1;
|
|
73
|
-
}
|
|
74
|
-
if (!Array.isArray(tasks) || !tasks.every(isBatchTask)) {
|
|
75
|
-
process.stderr.write('batch-plan: --tasks file must be a JSON array of {wbs, name}\n');
|
|
76
|
-
return 1;
|
|
77
|
-
}
|
|
78
|
-
process.stdout.write(`${JSON.stringify({ waves: buildBatchPlan(tasks) })}\n`);
|
|
79
|
-
return 0;
|
|
80
|
-
}
|
|
81
|
-
if (mode === 'order') {
|
|
82
|
-
let tasksPath = '';
|
|
83
|
-
let concurrency = 2;
|
|
84
|
-
for (let i = 1; i + 1 < argv.length; i += 2) {
|
|
85
|
-
if (argv[i] === '--tasks') tasksPath = argv[i + 1] ?? '';
|
|
86
|
-
else if (argv[i] === '--concurrency') concurrency = Number(argv[i + 1]);
|
|
87
|
-
}
|
|
88
|
-
if (tasksPath === '' || !Number.isInteger(concurrency) || concurrency < 1) {
|
|
89
|
-
process.stderr.write(`${BATCH_PLAN_USAGE}\n`);
|
|
90
|
-
return 2;
|
|
91
|
-
}
|
|
92
|
-
let tasks: unknown;
|
|
93
|
-
try {
|
|
94
|
-
tasks = readJsonFile(tasksPath);
|
|
95
|
-
} catch (error) {
|
|
96
|
-
process.stderr.write(
|
|
97
|
-
`batch-plan: cannot read ${tasksPath} — ${error instanceof Error ? error.message : String(error)}\n`,
|
|
98
|
-
);
|
|
99
|
-
return 1;
|
|
100
|
-
}
|
|
101
|
-
if (!Array.isArray(tasks) || !tasks.every(isScheduleTask)) {
|
|
102
|
-
process.stderr.write('batch-plan: --tasks file must be a JSON array of {wbs, dependencies?, files?}\n');
|
|
103
|
-
return 1;
|
|
104
|
-
}
|
|
105
|
-
process.stdout.write(`${JSON.stringify(scheduleBatch(tasks, { concurrency }))}\n`);
|
|
106
|
-
return 0;
|
|
107
|
-
}
|
|
108
|
-
if (mode === 'task-children') {
|
|
109
|
-
let letter = '';
|
|
110
|
-
let planPath = '';
|
|
111
|
-
for (let i = 1; i + 1 < argv.length; i += 2) {
|
|
112
|
-
if (argv[i] === '--letter') letter = argv[i + 1] ?? '';
|
|
113
|
-
else if (argv[i] === '--plan') planPath = argv[i + 1] ?? '';
|
|
114
|
-
}
|
|
115
|
-
if (!/^[A-Z]$/.test(letter) || planPath === '') {
|
|
116
|
-
process.stderr.write(`${BATCH_PLAN_USAGE}\n`);
|
|
117
|
-
return 2;
|
|
118
|
-
}
|
|
119
|
-
let payload: unknown;
|
|
120
|
-
try {
|
|
121
|
-
payload = readJsonFile(planPath);
|
|
122
|
-
} catch (error) {
|
|
123
|
-
process.stderr.write(
|
|
124
|
-
`batch-plan: cannot read ${planPath} — ${error instanceof Error ? error.message : String(error)}\n`,
|
|
125
|
-
);
|
|
126
|
-
return 1;
|
|
127
|
-
}
|
|
128
|
-
// Accept a bare plan array or a full `workflow show --format todo --json` payload.
|
|
129
|
-
const plan = Array.isArray(payload) ? payload : (payload as { plan?: PhasedPlanItem[] }).plan;
|
|
130
|
-
if (!Array.isArray(plan)) {
|
|
131
|
-
process.stderr.write(
|
|
132
|
-
'batch-plan: --plan file must be a plan array or a todo --json payload with a `plan` field\n',
|
|
133
|
-
);
|
|
134
|
-
return 1;
|
|
135
|
-
}
|
|
136
|
-
process.stdout.write(`${JSON.stringify(taskPhaseChildren(letter, plan as PhasedPlanItem[]))}\n`);
|
|
137
|
-
return 0;
|
|
138
|
-
}
|
|
139
|
-
process.stderr.write(`${BATCH_PLAN_USAGE}\n`);
|
|
140
|
-
return 2;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
if (import.meta.main) {
|
|
144
|
-
process.exit(main(process.argv.slice(2)));
|
|
145
|
-
}
|
|
@@ -1,423 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { createRequire } from "node:module";
|
|
3
|
-
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
4
|
-
|
|
5
|
-
// plugins/sp/lib/plan-projection.generated.mjs
|
|
6
|
-
function isTaskFrozen(status) {
|
|
7
|
-
return status?.toLowerCase() === "deprecated";
|
|
8
|
-
}
|
|
9
|
-
function undeprecateTaskCommand(wbs) {
|
|
10
|
-
return `spur task update ${wbs} todo`;
|
|
11
|
-
}
|
|
12
|
-
var NON_ABORTING_PREFLIGHT_CODES = new Set([
|
|
13
|
-
"L4.scenario-unverified",
|
|
14
|
-
"L4.verifying-incomplete-tasks"
|
|
15
|
-
]);
|
|
16
|
-
function classifyFeaturePreflightFindings(findings) {
|
|
17
|
-
const aborting = [];
|
|
18
|
-
const nonAborting = [];
|
|
19
|
-
for (const f of findings) {
|
|
20
|
-
if (f.severity === "error") {
|
|
21
|
-
if (NON_ABORTING_PREFLIGHT_CODES.has(f.code)) {
|
|
22
|
-
nonAborting.push(f);
|
|
23
|
-
} else {
|
|
24
|
-
aborting.push(f);
|
|
25
|
-
}
|
|
26
|
-
} else {
|
|
27
|
-
nonAborting.push(f);
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
return {
|
|
31
|
-
aborting,
|
|
32
|
-
nonAborting,
|
|
33
|
-
shouldAbort: aborting.length > 0
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
|
-
function quickReadiness(input) {
|
|
37
|
-
const status = (input.status ?? "").toLowerCase();
|
|
38
|
-
const operation = input.operation;
|
|
39
|
-
if (operation !== "run" && operation !== "refine" && operation !== "verify") {
|
|
40
|
-
return {
|
|
41
|
-
action: "invalid",
|
|
42
|
-
code: "IV",
|
|
43
|
-
reason: `quick-readiness: unknown operation '${operation}' (${input.wbs})`
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
if (input.filteredCount !== undefined) {
|
|
47
|
-
if (input.filteredCount < 0) {
|
|
48
|
-
return {
|
|
49
|
-
action: "invalid",
|
|
50
|
-
code: "IV",
|
|
51
|
-
reason: `quick-readiness: invalid negative filtered count '${input.filteredCount}' (${input.wbs})`
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
if (input.filteredCount === 0) {
|
|
55
|
-
return {
|
|
56
|
-
action: "skipped",
|
|
57
|
-
code: "EMPTY",
|
|
58
|
-
reason: `quick-readiness: empty status-filtered set — nothing to ${operation} (${input.wbs})`
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
if (isTaskFrozen(status)) {
|
|
63
|
-
return {
|
|
64
|
-
action: "skipped",
|
|
65
|
-
code: "DEPRECATED",
|
|
66
|
-
reason: `quick-readiness: deprecated (frozen) — no ${operation} hop; un-deprecate with \`${undeprecateTaskCommand(input.wbs)}\` (${input.wbs})`
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
if (status === "cancelled" || status === "done") {
|
|
70
|
-
if (operation === "verify" && input.force === true) {
|
|
71
|
-
return {
|
|
72
|
-
action: "runnable",
|
|
73
|
-
code: "FORCE",
|
|
74
|
-
reason: `quick-readiness: verify --force re-verification of ${status} task ${input.wbs}`
|
|
75
|
-
};
|
|
76
|
-
}
|
|
77
|
-
return {
|
|
78
|
-
action: "skipped",
|
|
79
|
-
code: status === "done" ? "DONE" : "CANCELLED",
|
|
80
|
-
reason: `quick-readiness: already ${status} — no ${operation} hop (${input.wbs})`
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
if (status === "blocked") {
|
|
84
|
-
return {
|
|
85
|
-
action: "blocked",
|
|
86
|
-
code: "BLK",
|
|
87
|
-
reason: `quick-readiness: blocked — human/handover first (${input.wbs})`
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
if (operation === "run" && input.dependencies && input.dependencies.length > 0) {
|
|
91
|
-
const unmet = input.dependencies.filter((d) => (input.depStatuses?.[d] ?? "missing").toLowerCase() !== "done");
|
|
92
|
-
if (unmet.length > 0) {
|
|
93
|
-
const frozen = unmet.filter((d) => isTaskFrozen(input.depStatuses?.[d]));
|
|
94
|
-
const hint = frozen.length > 0 ? ` — ${frozen.map((d) => `${d} deprecated (frozen); re-point the edge or un-deprecate: \`${undeprecateTaskCommand(d)}\``).join("; ")}` : "";
|
|
95
|
-
return {
|
|
96
|
-
action: "blocked",
|
|
97
|
-
code: "DEP",
|
|
98
|
-
reason: `quick-readiness: unmet deps — ${unmet.join(", ")} (${input.wbs})${hint}`,
|
|
99
|
-
unmetDeps: unmet
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
const required = input.requiredSections ?? [];
|
|
104
|
-
const present = input.presentSections ?? [];
|
|
105
|
-
const gaps = required.filter((s) => {
|
|
106
|
-
const finding = input.sectionFindings?.[s];
|
|
107
|
-
return !present.includes(s) || finding !== undefined && finding !== "";
|
|
108
|
-
});
|
|
109
|
-
if (operation === "refine") {
|
|
110
|
-
if (status !== "backlog" && status !== "todo") {
|
|
111
|
-
return {
|
|
112
|
-
action: "skipped",
|
|
113
|
-
code: "NONPLAN",
|
|
114
|
-
reason: `quick-readiness: refine targets backlog/todo only, not '${status}' (${input.wbs})`
|
|
115
|
-
};
|
|
116
|
-
}
|
|
117
|
-
return {
|
|
118
|
-
action: "runnable",
|
|
119
|
-
code: "OK",
|
|
120
|
-
reason: `quick-readiness: refine ready for ${input.wbs} (${gaps.length} planning gap(s) to fill)`
|
|
121
|
-
};
|
|
122
|
-
}
|
|
123
|
-
if (operation === "verify") {
|
|
124
|
-
if (status !== "testing" && status !== "wip") {
|
|
125
|
-
return {
|
|
126
|
-
action: "invalid",
|
|
127
|
-
code: "NOVERIFY",
|
|
128
|
-
reason: `quick-readiness: verify needs testing/wip, not '${status}' (${input.wbs})`
|
|
129
|
-
};
|
|
130
|
-
}
|
|
131
|
-
return {
|
|
132
|
-
action: "runnable",
|
|
133
|
-
code: "OK",
|
|
134
|
-
reason: `quick-readiness: verify ready for ${input.wbs}`
|
|
135
|
-
};
|
|
136
|
-
}
|
|
137
|
-
if (status !== "todo" && status !== "wip" && status !== "testing") {
|
|
138
|
-
return {
|
|
139
|
-
action: "invalid",
|
|
140
|
-
code: "NORUN",
|
|
141
|
-
reason: `quick-readiness: run needs todo/wip/testing, not '${status}' (${input.wbs})`
|
|
142
|
-
};
|
|
143
|
-
}
|
|
144
|
-
if (gaps.length > 0) {
|
|
145
|
-
return {
|
|
146
|
-
action: "needs-refinement",
|
|
147
|
-
code: "REFINE",
|
|
148
|
-
reason: `quick-readiness: implementation sections incomplete (${input.wbs})`,
|
|
149
|
-
gaps
|
|
150
|
-
};
|
|
151
|
-
}
|
|
152
|
-
return {
|
|
153
|
-
action: "runnable",
|
|
154
|
-
code: "OK",
|
|
155
|
-
reason: `quick-readiness: run ready for ${input.wbs}`
|
|
156
|
-
};
|
|
157
|
-
}
|
|
158
|
-
function preflightTask(input) {
|
|
159
|
-
const status = (input.status ?? "").toLowerCase();
|
|
160
|
-
const deps = input.dependencies ?? [];
|
|
161
|
-
const depStatuses = input.depStatuses ?? {};
|
|
162
|
-
if (status === "cancelled") {
|
|
163
|
-
return {
|
|
164
|
-
action: "skip",
|
|
165
|
-
code: "A9",
|
|
166
|
-
reason: `dev-next: cancelled — nothing to advance (${input.wbs})`
|
|
167
|
-
};
|
|
168
|
-
}
|
|
169
|
-
if (status === "done") {
|
|
170
|
-
return {
|
|
171
|
-
action: "skip",
|
|
172
|
-
code: "A8",
|
|
173
|
-
reason: `dev-next: already done — batch does not auto-wrap (${input.wbs})`
|
|
174
|
-
};
|
|
175
|
-
}
|
|
176
|
-
if (status === "blocked") {
|
|
177
|
-
return {
|
|
178
|
-
action: "skip",
|
|
179
|
-
code: "A7",
|
|
180
|
-
reason: `dev-next: blocked — do not launch pipeline; human/handover first (${input.wbs})`
|
|
181
|
-
};
|
|
182
|
-
}
|
|
183
|
-
if (isTaskFrozen(status)) {
|
|
184
|
-
return {
|
|
185
|
-
action: "skip",
|
|
186
|
-
code: "DEPRECATED",
|
|
187
|
-
reason: `dev-next: deprecated (frozen) — un-deprecate first: ${undeprecateTaskCommand(input.wbs)} (${input.wbs})`
|
|
188
|
-
};
|
|
189
|
-
}
|
|
190
|
-
if (status === "todo" || status === "backlog") {
|
|
191
|
-
const unmet = deps.filter((d) => {
|
|
192
|
-
const st = (depStatuses[d] ?? "missing").toLowerCase();
|
|
193
|
-
return st !== "done";
|
|
194
|
-
});
|
|
195
|
-
if (unmet.length > 0) {
|
|
196
|
-
return {
|
|
197
|
-
action: "skip",
|
|
198
|
-
code: "A2",
|
|
199
|
-
reason: `dev-next: blocked by deps — unmet: ${unmet.join(", ")} (${input.wbs})`,
|
|
200
|
-
unmetDeps: unmet
|
|
201
|
-
};
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
return { action: "run", code: "OK", reason: `preflight clear — launch task-pipeline for ${input.wbs}` };
|
|
205
|
-
}
|
|
206
|
-
var OPERATOR_TOOLS = new Set(["AskUserQuestion", "ask_user_question"]);
|
|
207
|
-
var SHELL_TOOLS = new Set(["bash", "shell", "exec", "exec_command", "run_command", "command"]);
|
|
208
|
-
var SUBAGENT_TOOLS = new Set(["agent", "task", "invoke_subagent", "subagent", "dispatch_agent"]);
|
|
209
|
-
var LOWERCASE_OPERATOR_TOOLS = new Set(["askuserquestion", "ask_user_question"]);
|
|
210
|
-
var SENSITIVE_PREFIXES = ["drizzle/", "packages/config/", ".github/", "plugins/sp/hooks/"];
|
|
211
|
-
var SENSITIVE_GLOBS = ["apps/server/src/**/auth*", "**/*secret*", "**/*.sql"];
|
|
212
|
-
function globToRegex(glob) {
|
|
213
|
-
const escaped = glob.replace(/[.+^${}()|[\]\\]/g, "\\$&").replaceAll("**/", "\x00").replaceAll("**", "\x01").replaceAll("*", "[^/]*").replaceAll("\x00", "(?:.*/)?").replaceAll("\x01", ".*");
|
|
214
|
-
return new RegExp(`^${escaped}$`, "s");
|
|
215
|
-
}
|
|
216
|
-
var SENSITIVE_MATCHERS = [
|
|
217
|
-
...SENSITIVE_PREFIXES.map((prefix) => ({
|
|
218
|
-
label: prefix,
|
|
219
|
-
test: (path) => path.startsWith(prefix)
|
|
220
|
-
})),
|
|
221
|
-
...SENSITIVE_GLOBS.map((glob) => {
|
|
222
|
-
const regex = globToRegex(glob);
|
|
223
|
-
return { label: glob, test: (path) => regex.test(path) };
|
|
224
|
-
})
|
|
225
|
-
];
|
|
226
|
-
|
|
227
|
-
// plugins/sp/scripts/batch-preflight.ts
|
|
228
|
-
function recoveryHint(status, wbs) {
|
|
229
|
-
const st = (status ?? "").toLowerCase();
|
|
230
|
-
switch (st) {
|
|
231
|
-
case "backlog":
|
|
232
|
-
return { code: "A1", command: `/sp:dev-refine ${wbs} --auto --next` };
|
|
233
|
-
case "todo":
|
|
234
|
-
return { code: "A3", command: `/sp:dev-run ${wbs} --auto --next` };
|
|
235
|
-
case "wip":
|
|
236
|
-
return { code: "A5", command: `/sp:dev-run ${wbs} --mode implement --auto --next` };
|
|
237
|
-
case "testing":
|
|
238
|
-
return { code: "A6", command: `/sp:dev-verify ${wbs} --auto --next` };
|
|
239
|
-
case "blocked":
|
|
240
|
-
return {
|
|
241
|
-
code: "A7",
|
|
242
|
-
command: `/sp:dev-handover "blocked task ${wbs} — see Notes/History"`
|
|
243
|
-
};
|
|
244
|
-
default:
|
|
245
|
-
return null;
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
function parsePreflightCliArgs(argv) {
|
|
249
|
-
let status = null;
|
|
250
|
-
let deps = [];
|
|
251
|
-
const depStatuses = {};
|
|
252
|
-
let wbs = "0000";
|
|
253
|
-
let recovery = false;
|
|
254
|
-
let help = false;
|
|
255
|
-
let json = false;
|
|
256
|
-
let operation = null;
|
|
257
|
-
let force = false;
|
|
258
|
-
let filteredCount = null;
|
|
259
|
-
let requiredSections = [];
|
|
260
|
-
let presentSections = [];
|
|
261
|
-
let classifyFeature = null;
|
|
262
|
-
for (let i = 0;i < argv.length; i++) {
|
|
263
|
-
const a = argv[i];
|
|
264
|
-
if (a === "--help" || a === "-h")
|
|
265
|
-
help = true;
|
|
266
|
-
else if (a === "--json")
|
|
267
|
-
json = true;
|
|
268
|
-
else if (a === "--recovery")
|
|
269
|
-
recovery = true;
|
|
270
|
-
else if (a === "--force")
|
|
271
|
-
force = true;
|
|
272
|
-
else if (a === "--operation") {
|
|
273
|
-
const v = argv[++i] ?? "";
|
|
274
|
-
operation = v === "run" || v === "refine" || v === "verify" ? v : null;
|
|
275
|
-
} else if (a === "--filtered-count") {
|
|
276
|
-
const v = Number(argv[++i]);
|
|
277
|
-
filteredCount = Number.isFinite(v) ? v : null;
|
|
278
|
-
} else if (a === "--required-sections") {
|
|
279
|
-
const raw = argv[++i] ?? "";
|
|
280
|
-
requiredSections = raw.length === 0 ? [] : raw.split(",").map((s) => s.trim()).filter(Boolean);
|
|
281
|
-
} else if (a === "--present-sections") {
|
|
282
|
-
const raw = argv[++i] ?? "";
|
|
283
|
-
presentSections = raw.length === 0 ? [] : raw.split(",").map((s) => s.trim()).filter(Boolean);
|
|
284
|
-
} else if (a === "--wbs")
|
|
285
|
-
wbs = argv[++i] ?? wbs;
|
|
286
|
-
else if (a === "--classify-feature") {
|
|
287
|
-
classifyFeature = argv[++i] ?? "[]";
|
|
288
|
-
} else if (a === "--status")
|
|
289
|
-
status = argv[++i] ?? null;
|
|
290
|
-
else if (a === "--deps") {
|
|
291
|
-
const raw = argv[++i] ?? "";
|
|
292
|
-
deps = raw.length === 0 ? [] : raw.split(",").map((s) => s.trim()).filter(Boolean);
|
|
293
|
-
} else if (a === "--dep-status") {
|
|
294
|
-
const raw = argv[++i] ?? "";
|
|
295
|
-
for (const part of raw.split(",")) {
|
|
296
|
-
const [k, v] = part.split(":");
|
|
297
|
-
if (k && v)
|
|
298
|
-
depStatuses[k.trim()] = v.trim();
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
return {
|
|
303
|
-
status,
|
|
304
|
-
deps,
|
|
305
|
-
depStatuses,
|
|
306
|
-
wbs,
|
|
307
|
-
recovery,
|
|
308
|
-
help,
|
|
309
|
-
json,
|
|
310
|
-
operation,
|
|
311
|
-
force,
|
|
312
|
-
filteredCount,
|
|
313
|
-
requiredSections,
|
|
314
|
-
presentSections,
|
|
315
|
-
classifyFeature
|
|
316
|
-
};
|
|
317
|
-
}
|
|
318
|
-
var PREFLIGHT_CLI_USAGE = `Usage:
|
|
319
|
-
bun plugins/sp/scripts/batch-preflight.ts --wbs <wbs> --status <status> \\
|
|
320
|
-
[--deps 0275,0276] [--dep-status 0275:done,0276:todo] [--recovery] [--json]
|
|
321
|
-
bun plugins/sp/scripts/batch-preflight.ts --operation <run|refine|verify> --wbs <wbs> --status <status> [--filtered-count <n>] [--required-sections A,B] [--present-sections A,B] [--force] [--json]
|
|
322
|
-
|
|
323
|
-
Exit: 0 = run (or recovery hint printed); 2 = skip; 1 = usage.`;
|
|
324
|
-
function runPreflightCli(argv) {
|
|
325
|
-
const args = parsePreflightCliArgs(argv);
|
|
326
|
-
if (args.help)
|
|
327
|
-
return { exitCode: 0, stdout: "", stderr: PREFLIGHT_CLI_USAGE };
|
|
328
|
-
if (args.classifyFeature !== null) {
|
|
329
|
-
try {
|
|
330
|
-
const findings = JSON.parse(args.classifyFeature);
|
|
331
|
-
const res = classifyFeaturePreflightFindings(Array.isArray(findings) ? findings : []);
|
|
332
|
-
return {
|
|
333
|
-
exitCode: res.shouldAbort ? 1 : 0,
|
|
334
|
-
stdout: `${JSON.stringify(res, null, 2)}
|
|
335
|
-
`,
|
|
336
|
-
stderr: ""
|
|
337
|
-
};
|
|
338
|
-
} catch (e) {
|
|
339
|
-
return {
|
|
340
|
-
exitCode: 1,
|
|
341
|
-
stdout: "",
|
|
342
|
-
stderr: `Invalid JSON for --classify-feature: ${String(e)}`
|
|
343
|
-
};
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
if (!args.status)
|
|
347
|
-
return { exitCode: 1, stdout: "", stderr: PREFLIGHT_CLI_USAGE };
|
|
348
|
-
if (args.recovery) {
|
|
349
|
-
const hint = recoveryHint(args.status, args.wbs);
|
|
350
|
-
const body = args.json ? `${JSON.stringify({ recovery: hint }, null, 2)}
|
|
351
|
-
` : hint ? `${hint.command}
|
|
352
|
-
` : `no recovery hop
|
|
353
|
-
`;
|
|
354
|
-
return { exitCode: 0, stdout: body, stderr: "" };
|
|
355
|
-
}
|
|
356
|
-
if (args.operation !== null) {
|
|
357
|
-
const result = quickReadiness({
|
|
358
|
-
wbs: args.wbs,
|
|
359
|
-
status: args.status,
|
|
360
|
-
operation: args.operation,
|
|
361
|
-
dependencies: args.deps,
|
|
362
|
-
depStatuses: args.depStatuses,
|
|
363
|
-
...args.filteredCount !== null ? { filteredCount: args.filteredCount } : {},
|
|
364
|
-
...args.requiredSections.length > 0 ? { requiredSections: args.requiredSections } : {},
|
|
365
|
-
...args.presentSections.length > 0 ? { presentSections: args.presentSections } : {},
|
|
366
|
-
...args.force ? { force: true } : {}
|
|
367
|
-
});
|
|
368
|
-
const runnable = result.action === "runnable" || result.action === "needs-refinement";
|
|
369
|
-
if (args.json) {
|
|
370
|
-
return { exitCode: runnable ? 0 : 2, stdout: `${JSON.stringify(result, null, 2)}
|
|
371
|
-
`, stderr: "" };
|
|
372
|
-
}
|
|
373
|
-
return {
|
|
374
|
-
exitCode: runnable ? 0 : 2,
|
|
375
|
-
stdout: `${result.action}${result.code ? ` ${result.code}` : ""}: ${result.reason}
|
|
376
|
-
`,
|
|
377
|
-
stderr: ""
|
|
378
|
-
};
|
|
379
|
-
}
|
|
380
|
-
const result = preflightTask({
|
|
381
|
-
wbs: args.wbs,
|
|
382
|
-
status: args.status,
|
|
383
|
-
dependencies: args.deps,
|
|
384
|
-
depStatuses: args.depStatuses
|
|
385
|
-
});
|
|
386
|
-
if (args.json) {
|
|
387
|
-
return {
|
|
388
|
-
exitCode: result.action === "run" ? 0 : 2,
|
|
389
|
-
stdout: `${JSON.stringify(result, null, 2)}
|
|
390
|
-
`,
|
|
391
|
-
stderr: ""
|
|
392
|
-
};
|
|
393
|
-
}
|
|
394
|
-
if (result.action === "run") {
|
|
395
|
-
return { exitCode: 0, stdout: `run: ${result.reason ?? "ok"}
|
|
396
|
-
`, stderr: "" };
|
|
397
|
-
}
|
|
398
|
-
return {
|
|
399
|
-
exitCode: 2,
|
|
400
|
-
stdout: `skip ${result.code}: ${result.reason}
|
|
401
|
-
`,
|
|
402
|
-
stderr: ""
|
|
403
|
-
};
|
|
404
|
-
}
|
|
405
|
-
{
|
|
406
|
-
const { exitCode, stdout, stderr } = runPreflightCli(process.argv.slice(2));
|
|
407
|
-
if (stdout)
|
|
408
|
-
process.stdout.write(stdout);
|
|
409
|
-
if (stderr)
|
|
410
|
-
process.stderr.write(`${stderr}
|
|
411
|
-
`);
|
|
412
|
-
process.exit(exitCode);
|
|
413
|
-
}
|
|
414
|
-
export {
|
|
415
|
-
NON_ABORTING_PREFLIGHT_CODES,
|
|
416
|
-
PREFLIGHT_CLI_USAGE,
|
|
417
|
-
classifyFeaturePreflightFindings,
|
|
418
|
-
parsePreflightCliArgs,
|
|
419
|
-
preflightTask,
|
|
420
|
-
quickReadiness,
|
|
421
|
-
recoveryHint,
|
|
422
|
-
runPreflightCli
|
|
423
|
-
};
|