@namewta/speculo 2.0.1 → 2.0.2
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 +40 -29
- package/dist/src/agent-skills.d.ts +43 -17
- package/dist/src/agent-skills.js +93 -26
- package/dist/src/agent-skills.js.map +1 -1
- package/dist/src/cli.js +43 -27
- package/dist/src/cli.js.map +1 -1
- package/dist/src/index.d.ts +20 -6
- package/dist/src/index.js +36 -11
- package/dist/src/index.js.map +1 -1
- package/dist/src/install-ui.d.ts +61 -0
- package/dist/src/install-ui.js +268 -0
- package/dist/src/install-ui.js.map +1 -0
- package/dist/src/interaction.d.ts +5 -0
- package/dist/src/interaction.js +10 -0
- package/dist/src/interaction.js.map +1 -0
- package/dist/src/transaction.d.ts +2 -0
- package/dist/src/transaction.js +16 -0
- package/dist/src/transaction.js.map +1 -1
- package/dist/src/workflows.js +2 -2
- package/dist/src/workflows.js.map +1 -1
- package/package.json +2 -2
- package/template/canonical/canonical-specdev-goal-plan.md +138 -78
- package/template/canonical/canonical-specdev-grill-with-docs.md +818 -184
- package/template/canonical/canonical-specdev-spec.md +819 -185
- package/template/canonical/canonical-specdev-tickets.md +690 -56
- package/template/canonical/canonical-specdev-wayfinder.md +818 -184
- package/template/references/coordination/coordination-contract.md +8 -8
- package/template/references/coordination/modes/cli.md +5 -5
- package/template/references/coordination/modes/herdr.md +16 -1
- package/template/references/coordination/result-and-recovery.md +2 -2
- package/template/references/coordination/task-brief.md +5 -5
- package/template/workflows/specdev/SKILL.md +2 -2
- package/template/workflows/specdev/common/rules/artifact-contract.md +2 -2
- package/template/workflows/specdev/common/rules/change-completion.md +4 -2
- package/template/workflows/specdev/common/rules/domain-runtime.md +2 -2
- package/template/workflows/specdev/common/rules/evidence-and-verification.md +2 -2
- package/template/workflows/specdev/common/rules/execution-policy.md +28 -0
- package/template/workflows/specdev/common/rules/operating-governance.md +2 -2
- package/template/workflows/specdev/common/rules/path-ownership.md +3 -3
- package/template/workflows/specdev/common/rules/workflow-routing.md +3 -3
- package/template/workflows/specdev/common/skills/code-review/SKILL.md +2 -0
- package/template/workflows/specdev/common/skills/dev-worktree/SKILL.md +11 -11
- package/template/workflows/specdev/common/skills/dev-worktree/references/create.md +20 -12
- package/template/workflows/specdev/common/skills/dev-worktree/references/finalize.md +12 -12
- package/template/workflows/specdev/common/tools/README.md +1 -1
- package/template/workflows/specdev/common/tools/change-artifacts.mjs +5 -11
- package/template/workflows/specdev/common/tools/plan-contract.mjs +11 -3
- package/template/workflows/specdev/common/tools/tests/engineering.test.mjs +2 -2
- package/template/workflows/specdev/common/tools/validate-specdev.mjs +131 -77
- package/template/workflows/specdev/manifest.json +4 -6
- package/template/workflows/specdev/specdev-c-code-review/SKILL.md +2 -2
- package/template/workflows/specdev/specdev-i-implement/SKILL.md +5 -5
- package/template/workflows/specdev/specdev-i-implement/assets/evidence-template.md +7 -6
- package/template/workflows/specdev/specdev-i-implement/references/execution-preflight.md +8 -4
- package/template/workflows/specdev/specdev-i-implement/references/implementation-procedure.md +17 -17
- package/template/workflows/specdev/specdev-i-init-setup/SKILL.md +1 -1
- package/template/workflows/specdev/specdev-l-learn/SKILL.md +35 -0
- package/template/workflows/specdev/specdev-l-learn/references/learning-change.md +23 -0
- package/template/workflows/specdev/specdev-l-learn/references/lesson-format.md +61 -0
- package/template/workflows/specdev/specdev-p-goal-plan/SKILL.md +2 -2
- package/template/workflows/specdev/specdev-p-goal-plan/assets/goal-plan-template.md +2 -0
- package/template/workflows/specdev/specdev-p-goal-plan/references/orchestration-protocol.md +1 -1
- package/template/workflows/specdev/specdev-p-goal-plan/references/planning-modes.md +2 -2
- package/template/workflows/specdev/specdev-p-goal-plan/references/single-change-plan.md +2 -2
- package/template/workflows/specdev/specdev-r-retro/SKILL.md +1 -1
- package/template/workflows/specdev/specdev-s-spec/SKILL.md +1 -1
- package/template/workflows/specdev/specdev-t-tickets/SKILL.md +1 -1
- package/template/workflows/specdev/specdev-t-tickets/assets/ticket-template.md +2 -2
- package/template/workflows/specdev/specdev-t-tickets/assets/tickets-map-template.md +1 -1
- package/template/workflows/specdev/specdev-t-tickets/references/planning-procedure.md +1 -1
- package/template/workflows/specdev/specdev-t-tickets/references/ticket-readiness.md +1 -1
- package/template/workflows/specdev/specdev-l-learn-change/SKILL.md +0 -33
- package/template/workflows/specdev/specdev-l-learn-change/references/lesson-format.md +0 -51
|
@@ -28,7 +28,7 @@ import {
|
|
|
28
28
|
validateGoalMap, validateInitiative, resolveProjectSource, readChangeExecution, readSpecification, resolveToolContext, validateExecutionPolicy, canonicalToolPath, resolveSnapshotContext, readLightweightGoal, validateAcceptanceEvidence,
|
|
29
29
|
} from "./plan-contract.mjs";
|
|
30
30
|
|
|
31
|
-
import { validateChangeClosure } from "./change-artifacts.mjs";
|
|
31
|
+
import { validateChangeClosure, readChangeOwner } from "./change-artifacts.mjs";
|
|
32
32
|
import { validateRecord, validateRecords } from "./delivery-artifacts.mjs";
|
|
33
33
|
import { reviewScopeCLI } from "./review-scope.mjs";
|
|
34
34
|
import { ENGINEERING_KINDS, validateEngineeringArtifacts, validateEngineeringPackage } from "./engineering-evidence.mjs";
|
|
@@ -55,7 +55,7 @@ const EXPECTED_WORKS = new Set([
|
|
|
55
55
|
"specdev-g-grill-with-docs",
|
|
56
56
|
"specdev-i-implement",
|
|
57
57
|
"specdev-i-init-setup",
|
|
58
|
-
"specdev-l-learn
|
|
58
|
+
"specdev-l-learn",
|
|
59
59
|
"specdev-p-goal-plan",
|
|
60
60
|
"specdev-p-prototype",
|
|
61
61
|
"specdev-r-review-architecture",
|
|
@@ -172,7 +172,7 @@ const VALID_STAGES = new Set([
|
|
|
172
172
|
"tickets",
|
|
173
173
|
"goal-plan",
|
|
174
174
|
"implement",
|
|
175
|
-
"learn
|
|
175
|
+
"learn",
|
|
176
176
|
"retro",
|
|
177
177
|
"pr-delivery",
|
|
178
178
|
"review",
|
|
@@ -874,10 +874,10 @@ function capabilityChecks(root) {
|
|
|
874
874
|
],
|
|
875
875
|
],
|
|
876
876
|
[
|
|
877
|
-
"learn
|
|
877
|
+
"learn",
|
|
878
878
|
[
|
|
879
|
-
join(root, "specdev-l-learn
|
|
880
|
-
["
|
|
879
|
+
join(root, "specdev-l-learn", "SKILL.md"),
|
|
880
|
+
["需求参考", "零专业背景", "$ARGUMENTS", "ASCII", "learning-change.md", "learning/index.md", "{number}_{topic}.md", "{roots.state}/learning/"],
|
|
881
881
|
],
|
|
882
882
|
],
|
|
883
883
|
[
|
|
@@ -1586,7 +1586,7 @@ function validateChangeLearning(change, required, errors) {
|
|
|
1586
1586
|
const diagramName = /^\d{2,}_[\p{L}\p{N}_-]+\.md$/u;
|
|
1587
1587
|
|
|
1588
1588
|
if (!existsSync(learningRoot)) {
|
|
1589
|
-
if (required) errors.push("learn
|
|
1589
|
+
if (required) errors.push("learn stage requires learning/index.md");
|
|
1590
1590
|
return null;
|
|
1591
1591
|
}
|
|
1592
1592
|
if (lstatSync(learningRoot).isSymbolicLink() || !isDirectory(learningRoot)) {
|
|
@@ -1595,22 +1595,27 @@ function validateChangeLearning(change, required, errors) {
|
|
|
1595
1595
|
}
|
|
1596
1596
|
|
|
1597
1597
|
const diagramFiles = [];
|
|
1598
|
+
const diagramNumbers = new Set();
|
|
1598
1599
|
for (const entry of readdirSync(learningRoot, { withFileTypes: true })) {
|
|
1599
1600
|
if (entry.isSymbolicLink()) {
|
|
1600
1601
|
errors.push(`learning/${entry.name}: learning artifacts must not be symlinks`);
|
|
1601
1602
|
} else if (entry.isFile() && diagramName.test(entry.name)) {
|
|
1602
1603
|
diagramFiles.push(entry.name);
|
|
1604
|
+
const number = Number(entry.name.split("_")[0]);
|
|
1605
|
+
if (diagramNumbers.has(number)) errors.push(`learning/${entry.name}: duplicate diagram number ${number}`);
|
|
1606
|
+
diagramNumbers.add(number);
|
|
1603
1607
|
}
|
|
1604
1608
|
}
|
|
1605
1609
|
diagramFiles.sort();
|
|
1606
1610
|
|
|
1607
1611
|
if (!isFile(indexPath)) {
|
|
1608
|
-
errors.push("learn
|
|
1612
|
+
errors.push("learn stage requires learning/index.md");
|
|
1609
1613
|
return null;
|
|
1610
1614
|
}
|
|
1611
1615
|
|
|
1612
1616
|
const index = readText(indexPath);
|
|
1613
|
-
|
|
1617
|
+
// Historical titles remain readable without rewriting existing learning records.
|
|
1618
|
+
if (!/^# (?:Change )?学习图解索引\s*$/m.test(index)) {
|
|
1614
1619
|
errors.push("learning/index.md: missing index heading");
|
|
1615
1620
|
}
|
|
1616
1621
|
const entries = Array.from(
|
|
@@ -2223,8 +2228,10 @@ function validateChangeExecution(change, errors, context) {
|
|
|
2223
2228
|
errors.push(`${basename(path)}: invalid worktree entry`);
|
|
2224
2229
|
continue;
|
|
2225
2230
|
}
|
|
2231
|
+
const direct = worktree.purpose === "direct-spec";
|
|
2232
|
+
const unit = direct ? "direct-spec" : worktree.ticket_id;
|
|
2226
2233
|
const requiredWorktreeKeys = [
|
|
2227
|
-
"ticket_id",
|
|
2234
|
+
...(direct ? ["purpose", "change", "integration_attempt_limit"] : ["ticket_id"]),
|
|
2228
2235
|
"owner",
|
|
2229
2236
|
"implementation_owner",
|
|
2230
2237
|
"integration_owner",
|
|
@@ -2245,14 +2252,24 @@ function validateChangeExecution(change, errors, context) {
|
|
|
2245
2252
|
}
|
|
2246
2253
|
const unexpectedWorktreeKeys = Object.keys(worktree).filter((key) => !requiredWorktreeKeys.includes(key));
|
|
2247
2254
|
if (unexpectedWorktreeKeys.length) {
|
|
2248
|
-
errors.push(`${basename(path)}: worktree ${
|
|
2255
|
+
errors.push(`${basename(path)}: worktree ${unit} has unexpected keys ${JSON.stringify(unexpectedWorktreeKeys.sort())}`);
|
|
2249
2256
|
}
|
|
2250
|
-
if (
|
|
2251
|
-
errors.push(
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2257
|
+
if (direct) {
|
|
2258
|
+
if (worktree.change !== expectedChange) errors.push("Direct Spec execution belongs to another change");
|
|
2259
|
+
try {
|
|
2260
|
+
if (readChangeOwner(context, expectedChange).owner !== worktree.owner) errors.push("Direct Spec execution owner differs from original Lead");
|
|
2261
|
+
} catch (error) { errors.push(error.message); }
|
|
2262
|
+
if (!Number.isInteger(worktree.integration_attempt_limit) || worktree.integration_attempt_limit < 1) errors.push("Direct Spec requires a positive integration_attempt_limit");
|
|
2263
|
+
if (worktree.integration?.attempts > worktree.integration_attempt_limit) errors.push("Direct Spec integration attempts exceed integration_attempt_limit");
|
|
2264
|
+
if (worktree.integration_owner !== worktree.owner) errors.push("Direct Spec integration owner must be Lead");
|
|
2265
|
+
if (worktree.status === "cancelled" && data.outcome !== "cancelled") errors.push("cancelled Direct Spec execution requires the original whole-change cancellation");
|
|
2266
|
+
if (worktree.workspace_ref !== "current" && worktree.branch !== `speculo/${expectedChange}/direct-spec`) errors.push("Direct Spec source branch must match its change");
|
|
2267
|
+
} else if (!/^T-[0-9]{2,}$/.test(String(unit))) {
|
|
2268
|
+
errors.push(`${basename(path)}: invalid worktree ticket_id ${unit}`);
|
|
2269
|
+
} else if (seenWorktreeTickets.has(unit)) {
|
|
2270
|
+
errors.push(`${basename(path)}: duplicate worktree for ${unit}`);
|
|
2271
|
+
}
|
|
2272
|
+
seenWorktreeTickets.add(unit);
|
|
2256
2273
|
const ref = worktree.workspace_ref;
|
|
2257
2274
|
if (
|
|
2258
2275
|
typeof ref !== "string" ||
|
|
@@ -2262,20 +2279,20 @@ function validateChangeExecution(change, errors, context) {
|
|
|
2262
2279
|
errors.push(`${basename(path)}: workspace_ref must be a portable non-absolute locator`);
|
|
2263
2280
|
continue;
|
|
2264
2281
|
}
|
|
2265
|
-
if (!VALID_WORKTREE_STATUS.has(worktree.status)) {
|
|
2282
|
+
if (!VALID_WORKTREE_STATUS.has(worktree.status) && !(direct && worktree.status === "cancelled")) {
|
|
2266
2283
|
errors.push(`${basename(path)}: invalid worktree status ${worktree.status}`);
|
|
2267
2284
|
}
|
|
2268
2285
|
const currentWorkspace = ref === "current";
|
|
2269
2286
|
if (worktree.provider !== "git") {
|
|
2270
|
-
errors.push(`${basename(path)}: Ticket worktree ${
|
|
2287
|
+
errors.push(`${basename(path)}: Ticket worktree ${unit} provider must be git`);
|
|
2271
2288
|
}
|
|
2272
|
-
const expectedRef = `specdev-worktree/${expectedChange}/${
|
|
2289
|
+
const expectedRef = `specdev-worktree/${expectedChange}/${unit}`;
|
|
2273
2290
|
if (!currentWorkspace && ref !== expectedRef) {
|
|
2274
2291
|
errors.push(`${basename(path)}: git workspace_ref must equal ${expectedRef}`);
|
|
2275
2292
|
}
|
|
2276
2293
|
|
|
2277
2294
|
if ("terminal_action" in worktree) {
|
|
2278
|
-
errors.push(`${basename(path)}: worktree ${
|
|
2295
|
+
errors.push(`${basename(path)}: worktree ${unit} obsolete terminal_action is not allowed`);
|
|
2279
2296
|
}
|
|
2280
2297
|
const requiredIntegrationKeys = [
|
|
2281
2298
|
"owner",
|
|
@@ -2287,35 +2304,35 @@ function validateChangeExecution(change, errors, context) {
|
|
|
2287
2304
|
];
|
|
2288
2305
|
const missingIntegrationKeys = requiredIntegrationKeys.filter((key) => !(key in worktree));
|
|
2289
2306
|
if (missingIntegrationKeys.length) {
|
|
2290
|
-
errors.push(`${basename(path)}: worktree ${
|
|
2307
|
+
errors.push(`${basename(path)}: worktree ${unit} integration contract missing ${JSON.stringify(missingIntegrationKeys)}`);
|
|
2291
2308
|
continue;
|
|
2292
2309
|
}
|
|
2293
2310
|
for (const key of ["owner", "implementation_owner", "integration_owner", "base_sha", "parent_branch", "branch", "updated_at"]) {
|
|
2294
2311
|
if (typeof worktree[key] !== "string" || !worktree[key].trim()) {
|
|
2295
|
-
errors.push(`${basename(path)}: worktree ${
|
|
2312
|
+
errors.push(`${basename(path)}: worktree ${unit} ${key} is required`);
|
|
2296
2313
|
}
|
|
2297
2314
|
}
|
|
2298
2315
|
if (typeof worktree.parent_branch !== "string" || !worktree.parent_branch.trim()) {
|
|
2299
|
-
errors.push(`${basename(path)}: worktree ${
|
|
2316
|
+
errors.push(`${basename(path)}: worktree ${unit} parent_branch is required`);
|
|
2300
2317
|
} else if (currentWorkspace && worktree.parent_branch !== worktree.branch) {
|
|
2301
|
-
errors.push(`${basename(path)}: current workspace ${
|
|
2318
|
+
errors.push(`${basename(path)}: current workspace ${unit} branch must equal parent_branch`);
|
|
2302
2319
|
} else if (!currentWorkspace && worktree.parent_branch === worktree.branch) {
|
|
2303
|
-
errors.push(`${basename(path)}: worktree ${
|
|
2320
|
+
errors.push(`${basename(path)}: worktree ${unit} parent_branch must differ from branch`);
|
|
2304
2321
|
}
|
|
2305
2322
|
|
|
2306
2323
|
const sourceRequired = new Set(["review", "integrating", "integrated", "removed"]).has(worktree.status);
|
|
2307
2324
|
if (sourceRequired && (typeof worktree.source_checkpoint !== "string" || !worktree.source_checkpoint.trim())) {
|
|
2308
|
-
errors.push(`${basename(path)}: worktree ${
|
|
2325
|
+
errors.push(`${basename(path)}: worktree ${unit} ${worktree.status} requires source_checkpoint`);
|
|
2309
2326
|
} else if (
|
|
2310
2327
|
worktree.source_checkpoint !== null &&
|
|
2311
2328
|
(typeof worktree.source_checkpoint !== "string" || !worktree.source_checkpoint.trim())
|
|
2312
2329
|
) {
|
|
2313
|
-
errors.push(`${basename(path)}: worktree ${
|
|
2330
|
+
errors.push(`${basename(path)}: worktree ${unit} source_checkpoint must be null or non-empty`);
|
|
2314
2331
|
}
|
|
2315
2332
|
|
|
2316
2333
|
const integration = worktree.integration;
|
|
2317
2334
|
if (!integration || typeof integration !== "object" || Array.isArray(integration)) {
|
|
2318
|
-
errors.push(`${basename(path)}: worktree ${
|
|
2335
|
+
errors.push(`${basename(path)}: worktree ${unit} integration must be an object`);
|
|
2319
2336
|
continue;
|
|
2320
2337
|
}
|
|
2321
2338
|
const requiredResultKeys = [
|
|
@@ -2339,49 +2356,48 @@ function validateChangeExecution(change, errors, context) {
|
|
|
2339
2356
|
];
|
|
2340
2357
|
const missingResultKeys = requiredResultKeys.filter((key) => !(key in integration));
|
|
2341
2358
|
if (missingResultKeys.length) {
|
|
2342
|
-
errors.push(`${basename(path)}: worktree ${
|
|
2359
|
+
errors.push(`${basename(path)}: worktree ${unit} integration missing ${JSON.stringify(missingResultKeys)}`);
|
|
2343
2360
|
}
|
|
2344
2361
|
const unexpectedResultKeys = Object.keys(integration).filter((key) => !requiredResultKeys.includes(key));
|
|
2345
2362
|
if (unexpectedResultKeys.length) {
|
|
2346
|
-
errors.push(`${basename(path)}: worktree ${
|
|
2363
|
+
errors.push(`${basename(path)}: worktree ${unit} integration has unexpected keys ${JSON.stringify(unexpectedResultKeys.sort())}`);
|
|
2347
2364
|
}
|
|
2348
2365
|
if (!VALID_INTEGRATION_STATUS.has(integration.status)) {
|
|
2349
|
-
errors.push(`${basename(path)}: worktree ${
|
|
2366
|
+
errors.push(`${basename(path)}: worktree ${unit} has invalid integration status ${integration.status}`);
|
|
2350
2367
|
}
|
|
2351
2368
|
if (!VALID_INTEGRATION_METHOD.has(integration.method)) {
|
|
2352
|
-
errors.push(`${basename(path)}: worktree ${
|
|
2369
|
+
errors.push(`${basename(path)}: worktree ${unit} has invalid integration method ${integration.method}`);
|
|
2353
2370
|
}
|
|
2354
2371
|
if (!VALID_INTEGRATION_VERIFICATION.has(integration.verification)) {
|
|
2355
|
-
errors.push(`${basename(path)}: worktree ${
|
|
2372
|
+
errors.push(`${basename(path)}: worktree ${unit} has invalid integration verification ${integration.verification}`);
|
|
2356
2373
|
}
|
|
2357
2374
|
if (!Array.isArray(integration.conflict_paths)) {
|
|
2358
|
-
errors.push(`${basename(path)}: worktree ${
|
|
2375
|
+
errors.push(`${basename(path)}: worktree ${unit} conflict_paths must be an array`);
|
|
2359
2376
|
}
|
|
2360
2377
|
if (!Number.isInteger(integration.attempts) || integration.attempts < 0) {
|
|
2361
|
-
errors.push(`${basename(path)}: worktree ${
|
|
2378
|
+
errors.push(`${basename(path)}: worktree ${unit} attempts must be a non-negative integer`);
|
|
2362
2379
|
}
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
) {
|
|
2367
|
-
errors.push(`${basename(path)}: worktree ${worktree.ticket_id} integration evidence must be a rooted Ticket Evidence path`);
|
|
2380
|
+
const expectedEvidence = `<Path>{roots.state}/specdev/changes/${expectedChange}/evidence/${unit}.md</Path>`;
|
|
2381
|
+
if (direct ? integration.evidence !== expectedEvidence : typeof integration.evidence !== "string" || !/^<Path>\{roots\.state\}\/specdev\/changes\/[^<]+\/evidence\/T-[0-9]{2,}\.md<\/Path>$/.test(integration.evidence)) {
|
|
2382
|
+
errors.push(`${basename(path)}: worktree ${unit} integration evidence must be its rooted Evidence path: ${expectedEvidence}`);
|
|
2368
2383
|
}
|
|
2369
2384
|
if (!new Set(["pending", "applying", "applied", "failed", "stale"]).has(integration.promotion_status)) {
|
|
2370
|
-
errors.push(`${basename(path)}: worktree ${
|
|
2385
|
+
errors.push(`${basename(path)}: worktree ${unit} has invalid promotion_status ${integration.promotion_status}`);
|
|
2371
2386
|
}
|
|
2372
2387
|
for (const [name, suite] of [["full_suite", integration.full_suite], ["e2e", integration.e2e]]) {
|
|
2373
2388
|
if (!suite || typeof suite !== "object" || Array.isArray(suite) || typeof suite.required !== "boolean" || !VALID_E2E_STATUS.has(suite.status)) {
|
|
2374
|
-
errors.push(`${basename(path)}: worktree ${
|
|
2389
|
+
errors.push(`${basename(path)}: worktree ${unit} ${name} contract is invalid`);
|
|
2375
2390
|
continue;
|
|
2376
2391
|
}
|
|
2377
2392
|
const unexpectedSuiteKeys = Object.keys(suite).filter((key) => !new Set(["required", "status", "reason", "evidence"]).has(key));
|
|
2378
|
-
if (unexpectedSuiteKeys.length) errors.push(`${basename(path)}: worktree ${
|
|
2393
|
+
if (unexpectedSuiteKeys.length) errors.push(`${basename(path)}: worktree ${unit} ${name} has unexpected keys ${JSON.stringify(unexpectedSuiteKeys.sort())}`);
|
|
2379
2394
|
if (suite.required === false && (suite.status !== "not-required" || typeof suite.reason !== "string" || !suite.reason.trim())) {
|
|
2380
|
-
errors.push(`${basename(path)}: worktree ${
|
|
2395
|
+
errors.push(`${basename(path)}: worktree ${unit} non-required ${name} needs not-required status and reason`);
|
|
2381
2396
|
}
|
|
2382
2397
|
if (suite.required === true && suite.status === "not-required") {
|
|
2383
|
-
errors.push(`${basename(path)}: worktree ${
|
|
2398
|
+
errors.push(`${basename(path)}: worktree ${unit} required ${name} cannot be not-required`);
|
|
2384
2399
|
}
|
|
2400
|
+
if (direct && suite.required === true && suite.status === "passed" && (typeof suite.evidence !== "string" || !suite.evidence.trim())) errors.push(`Direct Spec passed ${name} requires actual evidence`);
|
|
2385
2401
|
}
|
|
2386
2402
|
const e2e = integration.e2e;
|
|
2387
2403
|
if (
|
|
@@ -2391,65 +2407,65 @@ function validateChangeExecution(change, errors, context) {
|
|
|
2391
2407
|
typeof e2e.required !== "boolean" ||
|
|
2392
2408
|
!VALID_E2E_STATUS.has(e2e.status)
|
|
2393
2409
|
) {
|
|
2394
|
-
errors.push(`${basename(path)}: worktree ${
|
|
2410
|
+
errors.push(`${basename(path)}: worktree ${unit} integration e2e contract is invalid`);
|
|
2395
2411
|
} else {
|
|
2396
2412
|
const unexpectedE2eKeys = Object.keys(e2e).filter((key) => !new Set(["required", "status", "reason", "evidence"]).has(key));
|
|
2397
2413
|
if (unexpectedE2eKeys.length) {
|
|
2398
|
-
errors.push(`${basename(path)}: worktree ${
|
|
2414
|
+
errors.push(`${basename(path)}: worktree ${unit} integration e2e has unexpected keys ${JSON.stringify(unexpectedE2eKeys.sort())}`);
|
|
2399
2415
|
}
|
|
2400
2416
|
if (e2e.required === false && e2e.status !== "not-required") {
|
|
2401
|
-
errors.push(`${basename(path)}: worktree ${
|
|
2417
|
+
errors.push(`${basename(path)}: worktree ${unit} non-required E2E must use status=not-required`);
|
|
2402
2418
|
}
|
|
2403
2419
|
if (e2e.required === true && e2e.status === "not-required") {
|
|
2404
|
-
errors.push(`${basename(path)}: worktree ${
|
|
2420
|
+
errors.push(`${basename(path)}: worktree ${unit} required E2E cannot be not-required`);
|
|
2405
2421
|
}
|
|
2406
2422
|
if (
|
|
2407
2423
|
e2e.required === true &&
|
|
2408
2424
|
e2e.status === "passed" &&
|
|
2409
2425
|
(typeof e2e.evidence !== "string" || !e2e.evidence.trim())
|
|
2410
2426
|
) {
|
|
2411
|
-
errors.push(`${basename(path)}: worktree ${
|
|
2427
|
+
errors.push(`${basename(path)}: worktree ${unit} passed required E2E needs evidence`);
|
|
2412
2428
|
}
|
|
2413
2429
|
}
|
|
2414
2430
|
const integrationLifecycleActive = new Set(["integrating", "integrated", "removed"]).has(worktree.status);
|
|
2415
2431
|
if (integrationLifecycleActive) {
|
|
2416
2432
|
if (typeof integration.parent_before_sha !== "string" || !integration.parent_before_sha.trim()) {
|
|
2417
|
-
errors.push(`${basename(path)}: worktree ${
|
|
2433
|
+
errors.push(`${basename(path)}: worktree ${unit} ${worktree.status} requires parent_before_sha`);
|
|
2418
2434
|
}
|
|
2419
2435
|
if (typeof integration.source_sha !== "string" || !integration.source_sha.trim()) {
|
|
2420
|
-
errors.push(`${basename(path)}: worktree ${
|
|
2436
|
+
errors.push(`${basename(path)}: worktree ${unit} ${worktree.status} requires source_sha`);
|
|
2421
2437
|
} else if (integration.source_sha !== worktree.source_checkpoint) {
|
|
2422
|
-
errors.push(`${basename(path)}: worktree ${
|
|
2438
|
+
errors.push(`${basename(path)}: worktree ${unit} source_sha must equal source_checkpoint`);
|
|
2423
2439
|
}
|
|
2424
2440
|
if (currentWorkspace) {
|
|
2425
2441
|
if (integration.candidate_sha !== null || integration.candidate_tree_sha !== null || integration.candidate_branch !== null || integration.candidate_workspace_ref !== null) {
|
|
2426
|
-
errors.push(`${basename(path)}: current workspace ${
|
|
2442
|
+
errors.push(`${basename(path)}: current workspace ${unit} cannot record candidate workspace fields`);
|
|
2427
2443
|
}
|
|
2428
2444
|
if (integration.method !== "direct-parent") {
|
|
2429
|
-
errors.push(`${basename(path)}: current workspace ${
|
|
2445
|
+
errors.push(`${basename(path)}: current workspace ${unit} requires integration.method=direct-parent`);
|
|
2430
2446
|
}
|
|
2431
2447
|
} else {
|
|
2432
2448
|
if (typeof integration.candidate_sha !== "string" || !integration.candidate_sha.trim()) {
|
|
2433
|
-
errors.push(`${basename(path)}: worktree ${
|
|
2449
|
+
errors.push(`${basename(path)}: worktree ${unit} ${worktree.status} requires candidate_sha`);
|
|
2434
2450
|
}
|
|
2435
|
-
const expectedCandidateBranch = `speculo/integration/${expectedChange}/${
|
|
2451
|
+
const expectedCandidateBranch = `speculo/integration/${expectedChange}/${unit}`;
|
|
2436
2452
|
if (integration.candidate_branch !== expectedCandidateBranch) {
|
|
2437
|
-
errors.push(`${basename(path)}: worktree ${
|
|
2453
|
+
errors.push(`${basename(path)}: worktree ${unit} candidate_branch must equal ${expectedCandidateBranch}`);
|
|
2438
2454
|
}
|
|
2439
|
-
const expectedCandidateRef = `specdev-worktree/.integration/${expectedChange}/${
|
|
2455
|
+
const expectedCandidateRef = `specdev-worktree/.integration/${expectedChange}/${unit}`;
|
|
2440
2456
|
if (integration.candidate_workspace_ref !== expectedCandidateRef) {
|
|
2441
|
-
errors.push(`${basename(path)}: worktree ${
|
|
2457
|
+
errors.push(`${basename(path)}: worktree ${unit} candidate_workspace_ref must equal ${expectedCandidateRef}`);
|
|
2442
2458
|
}
|
|
2443
2459
|
if (!new Set(["fast-forward", "merge-commit"]).has(integration.method)) {
|
|
2444
|
-
errors.push(`${basename(path)}: worktree ${
|
|
2460
|
+
errors.push(`${basename(path)}: worktree ${unit} ${worktree.status} requires an integration method`);
|
|
2445
2461
|
}
|
|
2446
2462
|
}
|
|
2447
2463
|
if (!Number.isInteger(integration.attempts) || integration.attempts < 1) {
|
|
2448
|
-
errors.push(`${basename(path)}: worktree ${
|
|
2464
|
+
errors.push(`${basename(path)}: worktree ${unit} ${worktree.status} requires attempts >= 1`);
|
|
2449
2465
|
}
|
|
2450
2466
|
}
|
|
2451
2467
|
if (worktree.status === "integrating" && integration.status !== "candidate") {
|
|
2452
|
-
errors.push(`${basename(path)}: integrating worktree ${
|
|
2468
|
+
errors.push(`${basename(path)}: integrating worktree ${unit} requires integration.status=candidate`);
|
|
2453
2469
|
}
|
|
2454
2470
|
if (new Set(["integrated", "removed"]).has(worktree.status)) {
|
|
2455
2471
|
if (
|
|
@@ -2459,31 +2475,32 @@ function validateChangeExecution(change, errors, context) {
|
|
|
2459
2475
|
typeof integration.result_sha !== "string" ||
|
|
2460
2476
|
!integration.result_sha.trim() ||
|
|
2461
2477
|
(currentWorkspace ? integration.result_sha !== worktree.source_checkpoint : integration.result_sha !== integration.candidate_sha) ||
|
|
2478
|
+
(direct && (integration.promotion_status !== "applied" || !new Set(["not-required", "passed"]).has(integration.full_suite?.status))) ||
|
|
2462
2479
|
!e2e ||
|
|
2463
2480
|
!new Set(["not-required", "passed"]).has(e2e.status)
|
|
2464
2481
|
) {
|
|
2465
|
-
errors.push(`${basename(path)}: ${worktree.status} worktree ${
|
|
2482
|
+
errors.push(`${basename(path)}: ${worktree.status} worktree ${unit} requires passed candidate/result/E2E state`);
|
|
2466
2483
|
}
|
|
2467
2484
|
if (currentWorkspace && integration.method === "direct-parent") {
|
|
2468
2485
|
if (integration.candidate_sha !== null || integration.candidate_tree_sha !== null || integration.candidate_branch !== null || integration.candidate_workspace_ref !== null) {
|
|
2469
|
-
errors.push(`${basename(path)}: current workspace ${
|
|
2486
|
+
errors.push(`${basename(path)}: current workspace ${unit} direct-parent cannot record candidate fields`);
|
|
2470
2487
|
}
|
|
2471
2488
|
if (integration.result_sha !== worktree.source_checkpoint) {
|
|
2472
|
-
errors.push(`${basename(path)}: current workspace ${worktree.status} ${
|
|
2489
|
+
errors.push(`${basename(path)}: current workspace ${worktree.status} ${unit} result_sha must equal source_checkpoint`);
|
|
2473
2490
|
}
|
|
2474
2491
|
} else if (integration.method === "fast-forward") {
|
|
2475
2492
|
if (integration.candidate_sha !== worktree.source_checkpoint) {
|
|
2476
|
-
errors.push(`${basename(path)}: ${worktree.status} worktree ${
|
|
2493
|
+
errors.push(`${basename(path)}: ${worktree.status} worktree ${unit} fast-forward candidate/result must equal source_checkpoint`);
|
|
2477
2494
|
}
|
|
2478
2495
|
if (Array.isArray(integration.conflict_paths) && integration.conflict_paths.length > 0) {
|
|
2479
|
-
errors.push(`${basename(path)}: ${worktree.status} worktree ${
|
|
2496
|
+
errors.push(`${basename(path)}: ${worktree.status} worktree ${unit} fast-forward cannot record conflict_paths`);
|
|
2480
2497
|
}
|
|
2481
2498
|
} else if (
|
|
2482
2499
|
integration.method === "merge-commit" &&
|
|
2483
2500
|
typeof integration.candidate_sha === "string" &&
|
|
2484
2501
|
(integration.candidate_sha === worktree.source_checkpoint || integration.candidate_sha === integration.parent_before_sha)
|
|
2485
2502
|
) {
|
|
2486
|
-
errors.push(`${basename(path)}: ${worktree.status} worktree ${
|
|
2503
|
+
errors.push(`${basename(path)}: ${worktree.status} worktree ${unit} merge-commit candidate must be a distinct checkpoint`);
|
|
2487
2504
|
}
|
|
2488
2505
|
}
|
|
2489
2506
|
}
|
|
@@ -2491,8 +2508,8 @@ function validateChangeExecution(change, errors, context) {
|
|
|
2491
2508
|
}
|
|
2492
2509
|
|
|
2493
2510
|
function validateCurrentWorkspaceExecution(changeStatus, goalPlan, errors) {
|
|
2494
|
-
|
|
2495
|
-
const entries =
|
|
2511
|
+
const allEntries = Array.isArray(changeStatus?.worktrees) ? changeStatus.worktrees : [];
|
|
2512
|
+
const entries = goalPlan?.meta.ticket_workspace_policy === "current" ? allEntries : allEntries.filter(entry => entry.workspace_ref === "current");
|
|
2496
2513
|
const active = [];
|
|
2497
2514
|
for (const worktree of entries) {
|
|
2498
2515
|
if (worktree.workspace_ref !== "current") {
|
|
@@ -2502,7 +2519,7 @@ function validateCurrentWorkspaceExecution(changeStatus, goalPlan, errors) {
|
|
|
2502
2519
|
errors.push(`${worktree.ticket_id}: current workspace execution must commit directly on parent_branch`);
|
|
2503
2520
|
}
|
|
2504
2521
|
if (new Set(["active", "review", "integrating", "blocked"]).has(worktree.status)) {
|
|
2505
|
-
active.push(worktree.ticket_id);
|
|
2522
|
+
active.push(worktree.ticket_id ?? worktree.purpose);
|
|
2506
2523
|
}
|
|
2507
2524
|
const integration = worktree.integration;
|
|
2508
2525
|
if (integration && typeof integration === "object" && !Array.isArray(integration)) {
|
|
@@ -2549,7 +2566,7 @@ function validateGitEvidence(repoRoot, changeStatus, errors) {
|
|
|
2549
2566
|
}
|
|
2550
2567
|
const currentBranch = gitOutput(resolvedRoot, ["branch", "--show-current"]);
|
|
2551
2568
|
for (const worktree of Array.isArray(changeStatus?.worktrees) ? changeStatus.worktrees : []) {
|
|
2552
|
-
const label = String(worktree.ticket_id ?? "worktree");
|
|
2569
|
+
const label = String(worktree.ticket_id ?? worktree.purpose ?? "worktree");
|
|
2553
2570
|
for (const [name, sha] of [
|
|
2554
2571
|
["base_sha", worktree.base_sha],
|
|
2555
2572
|
["source_checkpoint", worktree.source_checkpoint],
|
|
@@ -2586,6 +2603,36 @@ function validateGitEvidence(repoRoot, changeStatus, errors) {
|
|
|
2586
2603
|
if (gitOutput(candidate,["rev-parse","HEAD"]) !== worktree.integration.candidate_sha || gitOutput(candidate,["rev-parse","HEAD^{tree}"]) !== worktree.integration.candidate_tree_sha) errors.push(`${label}: actual candidate workspace differs from the recorded checkpoint/tree`);
|
|
2587
2604
|
} catch(error) { errors.push(`${label}: actual candidate isolation is missing: ${error.message}`); }
|
|
2588
2605
|
}
|
|
2606
|
+
if (worktree.purpose === "direct-spec") {
|
|
2607
|
+
const integration = worktree.integration;
|
|
2608
|
+
if (worktree.source_checkpoint && gitOutput(resolvedRoot, ["rev-parse", worktree.branch]) !== worktree.source_checkpoint && worktree.status !== "removed") errors.push(`${label}: source branch tip differs from source_checkpoint`);
|
|
2609
|
+
if (worktree.source_checkpoint && !gitSucceeds(resolvedRoot, ["merge-base", "--is-ancestor", worktree.base_sha, worktree.source_checkpoint])) errors.push(`${label}: source_checkpoint must descend from base_sha`);
|
|
2610
|
+
if (worktree.workspace_ref !== "current" && new Set(["review", "integrating", "integrated"]).has(worktree.status)) {
|
|
2611
|
+
try {
|
|
2612
|
+
const source = runtime.safeWorkPathSync(resolvedRoot, worktree.workspace_ref, {mustExist:true,type:"directory"});
|
|
2613
|
+
if (gitOutput(source, ["status", "--porcelain"])) errors.push(`${label}: source worktree is dirty`);
|
|
2614
|
+
} catch (error) { errors.push(`${label}: source worktree missing before cleanup: ${error.message}`); }
|
|
2615
|
+
}
|
|
2616
|
+
if (integration?.candidate_sha) {
|
|
2617
|
+
if (gitOutput(resolvedRoot, ["rev-parse", `${integration.candidate_sha}^{tree}`]) !== integration.candidate_tree_sha) errors.push(`${label}: candidate tree differs from recorded tree`);
|
|
2618
|
+
for (const sha of [integration.parent_before_sha, worktree.source_checkpoint]) {
|
|
2619
|
+
if (sha && !gitSucceeds(resolvedRoot, ["merge-base", "--is-ancestor", sha, integration.candidate_sha])) errors.push(`${label}: candidate must contain parent and source checkpoints`);
|
|
2620
|
+
}
|
|
2621
|
+
}
|
|
2622
|
+
const parent = gitOutput(resolvedRoot, ["rev-parse", worktree.parent_branch]);
|
|
2623
|
+
if (worktree.status === "integrating" && worktree.workspace_ref !== "current") {
|
|
2624
|
+
try {
|
|
2625
|
+
const candidate = runtime.safeWorkPathSync(resolvedRoot, integration.candidate_workspace_ref, {mustExist:true,type:"directory"});
|
|
2626
|
+
if (gitOutput(candidate, ["rev-parse", "--path-format=absolute", "--git-common-dir"]) !== gitOutput(resolvedRoot, ["rev-parse", "--path-format=absolute", "--git-common-dir"]) || gitOutput(candidate, ["branch", "--show-current"]) !== integration.candidate_branch || gitOutput(candidate, ["status", "--porcelain"])) errors.push(`${label}: candidate must be a clean worktree of the recorded repository and branch`);
|
|
2627
|
+
} catch (error) { errors.push(`${label}: candidate cannot be inspected: ${error.message}`); }
|
|
2628
|
+
}
|
|
2629
|
+
if (worktree.status === "integrating" && parent !== integration?.parent_before_sha) errors.push(`${label}: parent HEAD drifted; candidate must be marked stale and rebuilt`);
|
|
2630
|
+
if (new Set(["failed", "stale"]).has(integration?.status) && parent === integration.candidate_sha && parent !== integration.parent_before_sha) errors.push(`${label}: failed or stale candidate has already advanced the parent; reconcile before continuing`);
|
|
2631
|
+
if (new Set(["integrated", "removed"]).has(worktree.status)) {
|
|
2632
|
+
if (parent !== integration?.result_sha) errors.push(`${label}: parent branch HEAD must equal recorded result_sha`);
|
|
2633
|
+
if (!gitSucceeds(resolvedRoot, ["merge-base", "--is-ancestor", worktree.source_checkpoint, parent])) errors.push(`${label}: parent must contain source_checkpoint`);
|
|
2634
|
+
}
|
|
2635
|
+
}
|
|
2589
2636
|
if (new Set(["integrated", "removed"]).has(worktree.status) && gitOutput(resolvedRoot, ["status", "--porcelain"])) {
|
|
2590
2637
|
errors.push(`${label}: repository is dirty while Ticket is recorded as ${worktree.status}`);
|
|
2591
2638
|
}
|
|
@@ -3083,7 +3130,7 @@ function validateChange(change, stage = null, repoRoot = null, engineeringKind =
|
|
|
3083
3130
|
validateReviews(change, stage === "review", errors);
|
|
3084
3131
|
validatePrototypes(change, stage === "prototype", errors, prototypeId, new Set(["spec", "tickets", "goal-plan", "implement", "review", "complete"]).has(stage), workspace.context);
|
|
3085
3132
|
if (new Set(["tickets", "goal-plan", "implement", "review", "complete"]).has(stage)) runDesignCheck("--handoff", change, errors, workspace.context);
|
|
3086
|
-
validateChangeLearning(change, stage === "learn
|
|
3133
|
+
validateChangeLearning(change, stage === "learn", errors);
|
|
3087
3134
|
const retro = validateRecords(change, "retrospective", parseFrontmatter);
|
|
3088
3135
|
const prs = validateRecords(change, "pull-request", parseFrontmatter);
|
|
3089
3136
|
errors.push(...retro.errors, ...prs.errors);
|
|
@@ -3206,10 +3253,17 @@ function validateChange(change, stage = null, repoRoot = null, engineeringKind =
|
|
|
3206
3253
|
}
|
|
3207
3254
|
}
|
|
3208
3255
|
}
|
|
3209
|
-
validateCurrentWorkspaceExecution(changeStatus, goalPlan, errors);
|
|
3210
3256
|
}
|
|
3257
|
+
validateCurrentWorkspaceExecution(changeStatus, goalPlan, errors);
|
|
3258
|
+
const directExecution = changeStatus?.worktrees?.find(entry => entry.purpose === "direct-spec");
|
|
3259
|
+
if (directExecution && workspace.config) errors.push(...validateExecutionPolicy({meta:{concurrent_implementation_limit:1,integration_attempt_limit:directExecution.integration_attempt_limit}},workspace.config).map(error => `Direct Spec: ${error}`));
|
|
3260
|
+
if (directExecution && (ticketMode || isParentImplementation)) errors.push("Direct Spec execution cannot coexist with Ticket or parent implementation artifacts");
|
|
3261
|
+
if (directExecution && (stage === "complete" || changeStatus.outcome === "completed") && !new Set(["integrated", "removed"]).has(directExecution.status)) errors.push("Direct Spec completion requires integrated or removed execution; active or failed candidates cannot complete");
|
|
3211
3262
|
validateGitEvidence(stage === "complete" ? workspace.context.projectRoot : repoRoot, changeStatus, errors);
|
|
3212
3263
|
|
|
3264
|
+
if (stage === "implement" && spec && !ticketMode && (spec.meta.ready_for_tickets !== true || spec.meta.status !== "ready")) errors.push("Direct Spec implementation requires a ready Spec with ready_for_tickets=true");
|
|
3265
|
+
if (spec?.meta.ready_for_tickets === true && !/\bAC-\d+\b/.test(spec.body)) errors.push("ready Spec must define at least one AC-### acceptance contract");
|
|
3266
|
+
|
|
3213
3267
|
if (stage === "complete" && spec && !ticketMode && !isParentImplementation && !retroOnly) errors.push(...validateAcceptanceEvidence(change,"evidence/direct-spec.md",[...new Set(spec.body.match(/\bAC-\d+\b/g) ?? [])]));
|
|
3214
3268
|
|
|
3215
3269
|
if (spec && ticketMode) {
|
|
@@ -3297,7 +3351,7 @@ function validateChange(change, stage = null, repoRoot = null, engineeringKind =
|
|
|
3297
3351
|
continue;
|
|
3298
3352
|
}
|
|
3299
3353
|
const text = readText(evidence);
|
|
3300
|
-
const currentWorkspace =
|
|
3354
|
+
const currentWorkspace = changeStatus?.worktrees?.find(entry => entry.ticket_id === ticketId)?.workspace_ref === "current";
|
|
3301
3355
|
const requiredHeadings = [
|
|
3302
3356
|
"## 2. Lead Dispatch And Candidate Return",
|
|
3303
3357
|
"## 3. 修改范围与路径所有权",
|
|
@@ -82,11 +82,9 @@
|
|
|
82
82
|
},
|
|
83
83
|
{
|
|
84
84
|
"id": "learn",
|
|
85
|
-
"after": [
|
|
86
|
-
"verify"
|
|
87
|
-
],
|
|
85
|
+
"after": [],
|
|
88
86
|
"inputs": [
|
|
89
|
-
"
|
|
87
|
+
"learning-request"
|
|
90
88
|
],
|
|
91
89
|
"outputs": [
|
|
92
90
|
"learning"
|
|
@@ -103,7 +101,7 @@
|
|
|
103
101
|
"specdev-g-grill-with-docs/SKILL.md",
|
|
104
102
|
"specdev-i-implement/SKILL.md",
|
|
105
103
|
"specdev-i-init-setup/SKILL.md",
|
|
106
|
-
"specdev-l-learn
|
|
104
|
+
"specdev-l-learn/SKILL.md",
|
|
107
105
|
"specdev-p-goal-plan/SKILL.md",
|
|
108
106
|
"specdev-p-prototype/SKILL.md",
|
|
109
107
|
"specdev-r-retro/SKILL.md",
|
|
@@ -134,7 +132,7 @@
|
|
|
134
132
|
"specdev-d-diagnose-bugs/SKILL.md": [],
|
|
135
133
|
"specdev-g-grill-with-docs/SKILL.md": [],
|
|
136
134
|
"specdev-i-init-setup/SKILL.md": [],
|
|
137
|
-
"specdev-l-learn
|
|
135
|
+
"specdev-l-learn/SKILL.md": [],
|
|
138
136
|
"specdev-p-prototype/SKILL.md": [],
|
|
139
137
|
"specdev-r-retro/SKILL.md": [],
|
|
140
138
|
"specdev-r-review-architecture/SKILL.md": [],
|
|
@@ -48,8 +48,8 @@ UI diff 按 <Path>{roots.workflows}/specdev/common/rules/design-contract.md</Pat
|
|
|
48
48
|
|
|
49
49
|
## 编排与执行上下文
|
|
50
50
|
|
|
51
|
-
|
|
51
|
+
进入实现或审查时读取 <Path>{roots.references}/coordination/coordination-contract.md</Path>;未明确指定时按当前宿主正常执行,App、CLI 等均可,subagent/agent team 属于宿主能力。明确指定 CLI 交互、Herdr 等方式时按选择执行并展开对应参考;编排与 current/worktree 独立,已有授权和选择直接复用。只读取所选工具的当前指南,不固定工具或模型。
|
|
52
52
|
|
|
53
|
-
`lead` 上下文维护原业务状态和 Evidence;`task` 上下文只处理 Lead 指定目标和阶段,执行同一方法并返回候选、findings、检查和未验证项,不修改正式 Ticket、Map、Goal、Evidence,不递归启动 Goal
|
|
53
|
+
`lead` 上下文维护原业务状态和 Evidence;`task` 上下文只处理 Lead 指定目标和阶段,执行同一方法并返回候选、findings、检查和未验证项,不修改正式 Ticket、Map、Goal、Evidence,不递归启动 Goal 或擅自跨 CLI 派单;宿主原生协作遵守原写集和单 writer 约束。未曝光到宿主列表不影响读取真实入口。
|
|
54
54
|
|
|
55
55
|
P 在已授权 run/resume 中完成当前实现、审查、验证和同范围修复后继续下一 Ready 任务,不逐票询问“继续”。只读分析或 plan 不执行。独立审查 required 时不能以 Lead 自审替代。
|
|
@@ -16,17 +16,17 @@ metadata:
|
|
|
16
16
|
|
|
17
17
|
先读 `<Path>{roots.workflows}/specdev/common/rules/activation-and-memory.md</Path>`;从 README/原 Goal 或当前 tickets-map/父 map 定位所需范围、本票、上游约束、项目 Skill 与执行策略。仅展开当前 Ticket 的正文、直接依赖、适用 Skill 和必要恢复证据,不整读知识库。
|
|
18
18
|
|
|
19
|
-
轻量实现可读取 README 的唯一 scope/acceptance/verification
|
|
19
|
+
轻量实现可读取 README 的唯一 scope/acceptance/verification 规格章节,等价于获批轻量 Direct Spec;独立完整 Spec 可按其全部风险门直接实施;保留明确范围、可判定 AC、真实验证及停止条件。存在独立 <Path>{roots.state}/specdev/changes/{change}/spec.md</Path> 时优先读取并拒绝双权威。无需独立票时不造 Ticket/Map;UI 的 DESIGN、批准和真实浏览器门保持。
|
|
20
20
|
|
|
21
21
|
## 当前票的执行入口
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
执行先读取 <Path>{roots.workflows}/specdev/common/rules/execution-policy.md</Path>,确定 Spec/Ticket、执行方式与工作区三个独立选择;完整获批 Spec 不因范围较大而强制拆票,已有 Ready Ticket 不强制补 Goal。执行必须读取 `<Path>{roots.workflows}/specdev/specdev-i-implement/references/implementation-procedure.md</Path>` 和 `<Path>{roots.workflows}/specdev/specdev-i-implement/references/execution-preflight.md</Path>`;执行过程按当前阶段路由设计、TDD、双轴审查和 Evidence;预检单独拥有 Git、workspace、Ready 与授权检查。两者必读,分支参考只在对应 workspace/风险/阶段进入时加载。
|
|
24
24
|
|
|
25
25
|
UI Ticket 或 Direct Spec 必须读取 <Path>{roots.workflows}/specdev/common/rules/design-contract.md</Path> 与当前批准 DESIGN,核验 design_ref(revision/digest/pages/acceptance)、原型和基线。页面任务按已批准规则实现,不重新选择模板;实际实现证据写入 `Design Conformance`,原型截图不替代生产页面验证。
|
|
26
26
|
|
|
27
27
|
1. 核验 Ready、授权、owner、真实源与 map 基线;新增计划型票按 `<Path>{roots.workflows}/specdev/common/rules/skill-invocation.md</Path>` 实际调用绑定能力并记录证据。票缺调用契约时先由 Lead 补齐,不猜测。
|
|
28
28
|
2. 保持 codebase-design、design-it-twice 和 TDD 的适用门禁;进入对应实现步骤再读其 reference。派单时调用 共享编排方法;Lead 是唯一 SpecDev 状态写入者。
|
|
29
|
-
3. current 模式串行使用当前 workspace
|
|
29
|
+
3. Ticket 和 Direct Spec 均可在 current 模式串行使用当前 workspace,或在 required 模式使用独立 worktree,E2E 由 Lead 在 current 或 required 的 parent-candidate 完成。实现 commit、父分支推进和集成均需真实授权。
|
|
30
30
|
4. 安全、并发、公共接口、数据迁移或用户要求全面审查时展开 `<Path>{roots.workflows}/specdev/common/skills/code-review/references/risk-review.md</Path>`;不为省上下文删减必要检查或限制发现数量。
|
|
31
31
|
5. 写 Evidence、运行适用校验并回读后才推进状态;必需 Skill 失败、验收失败、越界或归属冲突暂停本票及依赖它的分支。无关工作仍由总控继续,不接管他人事务。
|
|
32
32
|
|
|
@@ -42,8 +42,8 @@ UI Ticket 或 Direct Spec 必须读取 <Path>{roots.workflows}/specdev/common/ru
|
|
|
42
42
|
|
|
43
43
|
## 编排与执行上下文
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
进入实现或审查时读取 <Path>{roots.references}/coordination/coordination-contract.md</Path>;未明确指定时按当前宿主正常执行,App、CLI 等均可,subagent/agent team 属于宿主能力。明确指定 CLI 交互、Herdr 等方式时按选择执行并展开对应参考;编排与 current/worktree 独立,已有授权和选择直接复用。只读取所选工具的当前指南,不固定工具或模型。
|
|
46
46
|
|
|
47
|
-
`lead` 上下文维护原业务状态和 Evidence;`task` 上下文只处理 Lead 指定目标和阶段,执行同一方法并返回候选、findings、检查和未验证项,不修改正式 Ticket、Map、Goal、Evidence,不递归启动 Goal
|
|
47
|
+
`lead` 上下文维护原业务状态和 Evidence;`task` 上下文只处理 Lead 指定目标和阶段,执行同一方法并返回候选、findings、检查和未验证项,不修改正式 Ticket、Map、Goal、Evidence,不递归启动 Goal 或擅自跨 CLI 派单;宿主原生协作遵守原写集和单 writer 约束。未曝光到宿主列表不影响读取真实入口。
|
|
48
48
|
|
|
49
49
|
P 在已授权 run/resume 中完成当前实现、审查、验证和同范围修复后继续下一 Ready 任务,不逐票询问“继续”。只读分析或 plan 不执行。独立审查 required 时不能以 Lead 自审替代。
|