@xenonbyte/da-vinci-workflow 0.2.2 → 0.2.4

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.
Files changed (72) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/README.md +49 -14
  3. package/README.zh-CN.md +169 -14
  4. package/commands/claude/dv/breakdown.md +8 -0
  5. package/commands/claude/dv/build.md +16 -0
  6. package/commands/claude/dv/continue.md +4 -0
  7. package/commands/claude/dv/design.md +5 -2
  8. package/commands/claude/dv/tasks.md +14 -0
  9. package/commands/claude/dv/verify.md +11 -0
  10. package/commands/codex/prompts/dv-breakdown.md +8 -0
  11. package/commands/codex/prompts/dv-build.md +16 -0
  12. package/commands/codex/prompts/dv-continue.md +4 -0
  13. package/commands/codex/prompts/dv-design.md +5 -2
  14. package/commands/codex/prompts/dv-tasks.md +14 -0
  15. package/commands/codex/prompts/dv-verify.md +10 -0
  16. package/commands/gemini/dv/breakdown.toml +8 -0
  17. package/commands/gemini/dv/build.toml +16 -0
  18. package/commands/gemini/dv/continue.toml +4 -0
  19. package/commands/gemini/dv/design.toml +5 -2
  20. package/commands/gemini/dv/tasks.toml +14 -0
  21. package/commands/gemini/dv/verify.toml +10 -0
  22. package/commands/templates/dv-continue.shared.md +4 -0
  23. package/docs/discipline-and-orchestration-upgrade.md +83 -0
  24. package/docs/dv-command-reference.md +61 -2
  25. package/docs/execution-chain-migration.md +23 -0
  26. package/docs/execution-chain-plan.md +10 -3
  27. package/docs/mode-use-cases.md +2 -1
  28. package/docs/pencil-rendering-workflow.md +15 -12
  29. package/docs/prompt-entrypoints.md +5 -0
  30. package/docs/prompt-presets/README.md +1 -1
  31. package/docs/prompt-presets/desktop-app.md +3 -3
  32. package/docs/prompt-presets/mobile-app.md +3 -3
  33. package/docs/prompt-presets/tablet-app.md +3 -3
  34. package/docs/prompt-presets/web-app.md +3 -3
  35. package/docs/skill-usage.md +61 -38
  36. package/docs/workflow-examples.md +16 -13
  37. package/docs/workflow-overview.md +19 -0
  38. package/docs/zh-CN/dv-command-reference.md +59 -2
  39. package/docs/zh-CN/execution-chain-migration.md +23 -0
  40. package/docs/zh-CN/mode-use-cases.md +2 -1
  41. package/docs/zh-CN/pencil-rendering-workflow.md +15 -12
  42. package/docs/zh-CN/prompt-entrypoints.md +5 -0
  43. package/docs/zh-CN/prompt-presets/README.md +1 -1
  44. package/docs/zh-CN/prompt-presets/desktop-app.md +3 -3
  45. package/docs/zh-CN/prompt-presets/mobile-app.md +3 -3
  46. package/docs/zh-CN/prompt-presets/tablet-app.md +3 -3
  47. package/docs/zh-CN/prompt-presets/web-app.md +3 -3
  48. package/docs/zh-CN/skill-usage.md +61 -38
  49. package/docs/zh-CN/workflow-examples.md +15 -13
  50. package/docs/zh-CN/workflow-overview.md +19 -0
  51. package/examples/greenfield-spec-markupflow/.da-vinci/state/execution-signals/demo__lint-tasks.json +16 -0
  52. package/lib/audit-parsers.js +166 -10
  53. package/lib/audit.js +3 -26
  54. package/lib/cli.js +156 -2
  55. package/lib/design-source-registry.js +146 -0
  56. package/lib/execution-profile.js +143 -0
  57. package/lib/execution-signals.js +19 -1
  58. package/lib/lint-tasks.js +86 -2
  59. package/lib/planning-parsers.js +255 -18
  60. package/lib/save-current-design.js +790 -0
  61. package/lib/supervisor-review.js +3 -2
  62. package/lib/task-execution.js +160 -0
  63. package/lib/task-review.js +197 -0
  64. package/lib/verify.js +152 -1
  65. package/lib/workflow-bootstrap.js +2 -13
  66. package/lib/workflow-persisted-state.js +3 -1
  67. package/lib/workflow-state.js +503 -33
  68. package/lib/worktree-preflight.js +214 -0
  69. package/package.json +1 -1
  70. package/references/artifact-templates.md +56 -6
  71. package/tui/catalog.js +103 -0
  72. package/tui/index.js +2274 -418
@@ -0,0 +1,143 @@
1
+ const { unique } = require("./planning-parsers");
2
+
3
+ const ROLE_TAXONOMY = Object.freeze([
4
+ { id: "coordinator", description: "Own route truth, sequencing, and conflict resolution." },
5
+ { id: "implementer", description: "Deliver scoped code and docs changes for a task group." },
6
+ { id: "spec-reviewer", description: "Review task output against spec/plan requirements first." },
7
+ { id: "quality-reviewer", description: "Review quality, maintainability, and risk after spec pass." },
8
+ { id: "verifier", description: "Run verification commands and evidence freshness checks." }
9
+ ]);
10
+
11
+ const MODEL_TIER_GUIDANCE = Object.freeze({
12
+ coordinator: "high",
13
+ implementer: "balanced",
14
+ "spec-reviewer": "high",
15
+ "quality-reviewer": "high",
16
+ verifier: "balanced"
17
+ });
18
+
19
+ function normalizeOwnershipToken(value) {
20
+ const normalized = String(value || "")
21
+ .trim()
22
+ .replace(/\\/g, "/")
23
+ .replace(/^\.?\//, "");
24
+ if (!normalized) {
25
+ return "";
26
+ }
27
+ const segments = normalized.split("/").filter(Boolean);
28
+ if (segments.length === 0) {
29
+ return "";
30
+ }
31
+ if (segments.length === 1) {
32
+ return segments[0];
33
+ }
34
+ return `${segments[0]}/${segments[1]}`;
35
+ }
36
+
37
+ function collectOwnershipKeys(group) {
38
+ const fromTargets = Array.isArray(group.targetFiles) ? group.targetFiles : [];
39
+ const fromRefs = Array.isArray(group.fileReferences) ? group.fileReferences : [];
40
+ return unique([...fromTargets, ...fromRefs].map((item) => normalizeOwnershipToken(item)).filter(Boolean));
41
+ }
42
+
43
+ function buildOverlapMatrix(groups) {
44
+ const overlaps = [];
45
+ for (let left = 0; left < groups.length; left += 1) {
46
+ for (let right = left + 1; right < groups.length; right += 1) {
47
+ const leftGroup = groups[left];
48
+ const rightGroup = groups[right];
49
+ const shared = leftGroup.ownershipKeys.filter((token) => rightGroup.ownershipKeys.includes(token));
50
+ if (shared.length === 0) {
51
+ continue;
52
+ }
53
+ overlaps.push({
54
+ leftTaskGroupId: leftGroup.id,
55
+ rightTaskGroupId: rightGroup.id,
56
+ sharedOwnership: shared
57
+ });
58
+ }
59
+ }
60
+ return overlaps;
61
+ }
62
+
63
+ function deriveExecutionMode(stage, groups, overlaps) {
64
+ if (stage !== "build" && stage !== "verify") {
65
+ return "serial";
66
+ }
67
+ if (overlaps.length > 0) {
68
+ return "serial";
69
+ }
70
+ if (groups.some((group) => group.executionIntent.includes("review_required"))) {
71
+ return "review_heavy";
72
+ }
73
+ if (groups.length <= 1) {
74
+ return "serial";
75
+ }
76
+ return "bounded_parallel";
77
+ }
78
+
79
+ function deriveExecutionProfile(options = {}) {
80
+ const stage = String(options.stage || "");
81
+ const sourceTaskGroups = Array.isArray(options.taskGroups) ? options.taskGroups : [];
82
+ const normalizedGroups = sourceTaskGroups.map((group) => ({
83
+ id: group.taskGroupId || group.id || "",
84
+ title: group.title || "",
85
+ executionIntent: Array.isArray(group.executionIntent) ? group.executionIntent : [],
86
+ ownershipKeys: collectOwnershipKeys(group),
87
+ reviewIntent: group.reviewIntent === true
88
+ }));
89
+ const groups = normalizedGroups.filter((group) => group.id);
90
+ const overlaps = buildOverlapMatrix(groups);
91
+ const mode = deriveExecutionMode(stage, groups, overlaps);
92
+
93
+ let maxParallel = 1;
94
+ if (mode === "bounded_parallel") {
95
+ maxParallel = Math.min(3, Math.max(2, groups.length));
96
+ } else if (mode === "review_heavy") {
97
+ maxParallel = 2;
98
+ }
99
+
100
+ const rationale = [];
101
+ if (mode === "serial") {
102
+ if (overlaps.length > 0) {
103
+ rationale.push("ownership overlap detected across task groups");
104
+ } else if (stage !== "build" && stage !== "verify") {
105
+ rationale.push(`stage ${stage || "unknown"} prefers serial progression`);
106
+ } else {
107
+ rationale.push("single task-group or unspecified execution intent");
108
+ }
109
+ }
110
+ if (mode === "bounded_parallel") {
111
+ rationale.push("independent ownership detected across task groups");
112
+ }
113
+ if (mode === "review_heavy") {
114
+ rationale.push("review-required execution intent detected");
115
+ }
116
+
117
+ return {
118
+ advisory: true,
119
+ mode,
120
+ stage,
121
+ maxParallel,
122
+ roles: ROLE_TAXONOMY,
123
+ modelTierGuidance: MODEL_TIER_GUIDANCE,
124
+ overlaps,
125
+ rationale,
126
+ taskGroups: groups.map((group) => ({
127
+ taskGroupId: group.id,
128
+ title: group.title,
129
+ ownershipKeys: group.ownershipKeys,
130
+ executionIntent: group.executionIntent,
131
+ recommendedMode:
132
+ mode === "bounded_parallel" && group.executionIntent.includes("serial")
133
+ ? "serial"
134
+ : mode
135
+ }))
136
+ };
137
+ }
138
+
139
+ module.exports = {
140
+ ROLE_TAXONOMY,
141
+ MODEL_TIER_GUIDANCE,
142
+ deriveExecutionProfile
143
+ };
@@ -48,6 +48,7 @@ function writeExecutionSignal(projectRoot, payload) {
48
48
  failures: Array.isArray(payload.failures) ? payload.failures : [],
49
49
  warnings: Array.isArray(payload.warnings) ? payload.warnings : [],
50
50
  notes: Array.isArray(payload.notes) ? payload.notes : [],
51
+ details: payload && payload.details !== undefined ? payload.details : null,
51
52
  timestamp: new Date().toISOString(),
52
53
  changeId
53
54
  };
@@ -129,8 +130,25 @@ function summarizeSignalsBySurface(signals) {
129
130
  return summary;
130
131
  }
131
132
 
133
+ function listSignalsBySurfacePrefix(signals, prefix) {
134
+ const normalizedPrefix = sanitizeSurfaceName(prefix);
135
+ if (!normalizedPrefix) {
136
+ return [];
137
+ }
138
+ return (signals || [])
139
+ .filter((signal) => sanitizeSurfaceName(signal.surface || "").startsWith(normalizedPrefix))
140
+ .sort((left, right) => String(right.timestamp || "").localeCompare(String(left.timestamp || "")));
141
+ }
142
+
143
+ function getLatestSignalBySurfacePrefix(signals, prefix) {
144
+ const matches = listSignalsBySurfacePrefix(signals, prefix);
145
+ return matches.length > 0 ? matches[0] : null;
146
+ }
147
+
132
148
  module.exports = {
133
149
  writeExecutionSignal,
134
150
  readExecutionSignals,
135
- summarizeSignalsBySurface
151
+ summarizeSignalsBySurface,
152
+ listSignalsBySurfacePrefix,
153
+ getLatestSignalBySurfacePrefix
136
154
  };
package/lib/lint-tasks.js CHANGED
@@ -85,6 +85,13 @@ function lintTasks(projectPathInput, options = {}) {
85
85
 
86
86
  result.summary.groups = parsedTasks.taskGroups.length;
87
87
  result.summary.checklistItems = parsedTasks.checklistItems.length;
88
+ result.summary.discipline = {
89
+ groupsMissingTargets: 0,
90
+ groupsMissingExecutionIntent: 0,
91
+ groupsMissingVerificationCommands: 0,
92
+ groupsWithPlaceholders: 0,
93
+ groupsMissingTestingIntent: 0
94
+ };
88
95
 
89
96
  if (parsedTasks.taskGroups.length === 0) {
90
97
  result.failures.push("`tasks.md` is missing top-level numbered task groups (for example `## 1. Setup`).");
@@ -109,12 +116,86 @@ function lintTasks(projectPathInput, options = {}) {
109
116
  }
110
117
 
111
118
  const hasVerificationAction =
112
- parsedTasks.taskGroups.some((group) => /verify|verification|coverage/i.test(group.title)) ||
113
- parsedTasks.checklistItems.some((item) => /verify|verification|coverage/i.test(item.text));
119
+ parsedTasks.taskGroups.some(
120
+ (group) =>
121
+ /verify|verification|coverage/i.test(group.title) ||
122
+ (Array.isArray(group.verificationActions) && group.verificationActions.length > 0)
123
+ ) || parsedTasks.checklistItems.some((item) => /verify|verification|coverage/i.test(item.text));
114
124
  if (!hasVerificationAction) {
115
125
  result.warnings.push("Missing explicit verification actions in `tasks.md`.");
116
126
  }
117
127
 
128
+ if (parsedTasks.markers && Array.isArray(parsedTasks.markers.malformed) && parsedTasks.markers.malformed.length > 0) {
129
+ for (const malformed of parsedTasks.markers.malformed) {
130
+ result.warnings.push(
131
+ `Malformed discipline marker in tasks at line ${malformed.line}: ${malformed.reason}`
132
+ );
133
+ }
134
+ }
135
+
136
+ const markerSummary = parsedTasks.markerSummary || {};
137
+ if (!markerSummary.hasPlanSelfReview) {
138
+ result.warnings.push(
139
+ "Missing `plan_self_review` discipline marker in `tasks.md` (legacy plans may keep advisory fallback)."
140
+ );
141
+ }
142
+ if (!markerSummary.hasOperatorReviewAck) {
143
+ result.warnings.push(
144
+ "Missing `operator_review_ack` discipline marker in `tasks.md` (legacy plans may keep advisory fallback)."
145
+ );
146
+ }
147
+
148
+ for (const group of parsedTasks.taskGroups) {
149
+ const groupLabel = `${group.id}. ${group.title}`;
150
+ const hasTargets = Array.isArray(group.targetFiles) && group.targetFiles.length > 0;
151
+ const hasFileRefs = Array.isArray(group.fileReferences) && group.fileReferences.length > 0;
152
+ if (!hasTargets && !hasFileRefs) {
153
+ result.summary.discipline.groupsMissingTargets += 1;
154
+ result.warnings.push(
155
+ `Task group ${groupLabel} is missing exact file targets or code-area references.`
156
+ );
157
+ }
158
+
159
+ if (!Array.isArray(group.executionIntent) || group.executionIntent.length === 0) {
160
+ result.summary.discipline.groupsMissingExecutionIntent += 1;
161
+ result.warnings.push(
162
+ `Task group ${groupLabel} is missing execution-mode hints (serial, bounded parallel, or review-required).`
163
+ );
164
+ }
165
+
166
+ const hasVerificationCommands =
167
+ Array.isArray(group.verificationCommands) && group.verificationCommands.length > 0;
168
+ const hasVerificationIntent =
169
+ /verify|verification|coverage|test/i.test(group.title) ||
170
+ (Array.isArray(group.verificationActions) && group.verificationActions.length > 0);
171
+ if (hasVerificationIntent && !hasVerificationCommands) {
172
+ result.summary.discipline.groupsMissingVerificationCommands += 1;
173
+ result.warnings.push(
174
+ `Task group ${groupLabel} has verification intent but no explicit verification command (for example \`npm test\`).`
175
+ );
176
+ }
177
+
178
+ if (Array.isArray(group.placeholderItems) && group.placeholderItems.length > 0) {
179
+ result.summary.discipline.groupsWithPlaceholders += 1;
180
+ result.warnings.push(
181
+ `Task group ${groupLabel} contains placeholder wording (${group.placeholderItems.length} item(s)).`
182
+ );
183
+ }
184
+
185
+ if (group.codeChangeLikely && !group.testingIntent) {
186
+ result.summary.discipline.groupsMissingTestingIntent += 1;
187
+ result.warnings.push(
188
+ `Task group ${groupLabel} appears to change code behavior but omits testing intent.`
189
+ );
190
+ }
191
+
192
+ if (Array.isArray(group.executionIntent) && group.executionIntent.includes("review_required") && !group.reviewIntent) {
193
+ result.warnings.push(
194
+ `Task group ${groupLabel} hints review-required execution but does not declare concrete review intent.`
195
+ );
196
+ }
197
+ }
198
+
118
199
  for (const specRecord of specRecords) {
119
200
  const behaviorItems = specRecord.parsed.sections.behavior.items || [];
120
201
  for (const behaviorItem of behaviorItems) {
@@ -135,6 +216,9 @@ function lintTasks(projectPathInput, options = {}) {
135
216
  }
136
217
 
137
218
  result.notes.push("lint-tasks defaults to advisory mode; pass `--strict` to block on findings.");
219
+ result.notes.push(
220
+ "Strict-promotion guidance: require clean placeholder/file-target/execution-intent/verification-command findings before promoting to build."
221
+ );
138
222
  return finalize(result);
139
223
  }
140
224
 
@@ -1,7 +1,11 @@
1
1
  const fs = require("fs");
2
2
  const path = require("path");
3
3
  const crypto = require("crypto");
4
- const { getMarkdownSection } = require("./audit-parsers");
4
+ const {
5
+ getMarkdownSection,
6
+ parseDisciplineMarkers,
7
+ DISCIPLINE_MARKER_NAMES
8
+ } = require("./audit-parsers");
5
9
  const { parseRuntimeSpecMarkdown } = require("./artifact-parsers");
6
10
  const { pathExists, readTextIfExists } = require("./utils");
7
11
 
@@ -350,33 +354,239 @@ function parsePageMapArtifact(text) {
350
354
  };
351
355
  }
352
356
 
357
+ function parseInlineCodeTokens(line) {
358
+ const tokens = [];
359
+ const matches = String(line || "").matchAll(/`([^`]+)`/g);
360
+ for (const match of matches) {
361
+ const token = String(match[1] || "").trim();
362
+ if (token) {
363
+ tokens.push(token);
364
+ }
365
+ }
366
+ return tokens;
367
+ }
368
+
369
+ function normalizeExecutionIntent(text) {
370
+ const normalized = normalizeText(text);
371
+ if (!normalized) {
372
+ return "";
373
+ }
374
+ if (/bounded parallel|parallel bounded|parallel-safe/.test(normalized)) {
375
+ return "bounded_parallel";
376
+ }
377
+ if (/serial|sequential|one by one/.test(normalized)) {
378
+ return "serial";
379
+ }
380
+ if (/review required|review-required|review heavy|spec review|quality review/.test(normalized)) {
381
+ return "review_required";
382
+ }
383
+ return "";
384
+ }
385
+
386
+ function hasVerificationIntent(text) {
387
+ return /verify|verification|coverage|assert|validate|smoke/i.test(String(text || ""));
388
+ }
389
+
390
+ function hasTestingIntent(text) {
391
+ return /(?:^|\b)(test|tests|unit test|integration test|e2e|regression|tdd|coverage)(?:\b|$)/i.test(
392
+ String(text || "")
393
+ );
394
+ }
395
+
396
+ function hasReviewIntent(text) {
397
+ return /review|reviewer|spec review|quality review|qa|audit/i.test(String(text || ""));
398
+ }
399
+
400
+ function looksLikeCodeChange(text) {
401
+ return /implement|modify|update|add|create|refactor|fix|remove|rename|rewrite|harden|extend/i.test(
402
+ String(text || "")
403
+ );
404
+ }
405
+
406
+ function isPlaceholderText(text) {
407
+ return /\b(TBD|TODO|implement later|later fill|to be decided)\b/i.test(String(text || ""));
408
+ }
409
+
410
+ function extractFileReferences(text) {
411
+ const references = [];
412
+ const seen = new Set();
413
+ const addReference = (value) => {
414
+ const candidate = String(value || "")
415
+ .replace(/[`"'(),]/g, "")
416
+ .trim();
417
+ if (!candidate) {
418
+ return;
419
+ }
420
+ if (!/[./]/.test(candidate)) {
421
+ return;
422
+ }
423
+ if (candidate.length < 3) {
424
+ return;
425
+ }
426
+ if (seen.has(candidate)) {
427
+ return;
428
+ }
429
+ seen.add(candidate);
430
+ references.push(candidate);
431
+ };
432
+
433
+ for (const token of parseInlineCodeTokens(text)) {
434
+ addReference(token);
435
+ }
436
+ const plainMatches = String(text || "").matchAll(/(?:^|[\s(])([A-Za-z0-9._/-]+(?:\.[A-Za-z0-9_*.-]+|\/))(?:$|[\s),])/g);
437
+ for (const match of plainMatches) {
438
+ addReference(match[1]);
439
+ }
440
+ return references;
441
+ }
442
+
443
+ function extractVerificationCommands(text) {
444
+ const commands = [];
445
+ const supportedCommandPattern =
446
+ /^(?:da-vinci\s+verify-(?:bindings|implementation|structure|coverage)|npm|pnpm|yarn|bun|node|npx|pytest|go test|cargo test|dotnet test|mvn test|gradle test|make test|vitest|jest)\b/i;
447
+ const addCommand = (value) => {
448
+ const command = String(value || "").trim();
449
+ if (!command) {
450
+ return;
451
+ }
452
+ if (!/\s/.test(command)) {
453
+ return;
454
+ }
455
+ if (!supportedCommandPattern.test(command)) {
456
+ return;
457
+ }
458
+ commands.push(command);
459
+ };
460
+ for (const token of parseInlineCodeTokens(text)) {
461
+ addCommand(token);
462
+ }
463
+ return unique(commands);
464
+ }
465
+
466
+ function analyzeTaskGroup(section) {
467
+ const targetFiles = [];
468
+ const verificationActions = [];
469
+ const verificationCommands = [];
470
+ const placeholderItems = [];
471
+ const rawFileReferences = [];
472
+ const executionHints = [];
473
+
474
+ const lines = Array.isArray(section.lines) ? section.lines : [];
475
+ for (let index = 0; index < lines.length; index += 1) {
476
+ const line = String(lines[index] || "");
477
+ rawFileReferences.push(...extractFileReferences(line));
478
+ if (isPlaceholderText(line)) {
479
+ placeholderItems.push(line.trim());
480
+ }
481
+ if (hasVerificationIntent(line)) {
482
+ verificationActions.push(line.trim());
483
+ }
484
+ verificationCommands.push(...extractVerificationCommands(line));
485
+ const executionIntent = normalizeExecutionIntent(line);
486
+ if (executionIntent) {
487
+ executionHints.push(executionIntent);
488
+ }
489
+
490
+ if (/^\s*target files\s*:\s*$/i.test(line)) {
491
+ for (let cursor = index + 1; cursor < lines.length; cursor += 1) {
492
+ const followLine = String(lines[cursor] || "");
493
+ if (/^\s*$/.test(followLine)) {
494
+ continue;
495
+ }
496
+ if (/^\s{0,3}##\s+/.test(followLine)) {
497
+ break;
498
+ }
499
+ if (/^\s*[A-Za-z][A-Za-z0-9 _-]{0,80}\s*:\s*$/.test(followLine)) {
500
+ break;
501
+ }
502
+ const itemMatch = followLine.match(/^\s*-\s+(.+)$/);
503
+ if (!itemMatch) {
504
+ break;
505
+ }
506
+ const entry = String(itemMatch[1] || "").trim();
507
+ if (!entry) {
508
+ continue;
509
+ }
510
+ targetFiles.push(...extractFileReferences(entry));
511
+ }
512
+ }
513
+ }
514
+
515
+ for (const item of section.checklistItems) {
516
+ rawFileReferences.push(...extractFileReferences(item.text));
517
+ if (isPlaceholderText(item.text)) {
518
+ placeholderItems.push(item.text);
519
+ }
520
+ if (hasVerificationIntent(item.text)) {
521
+ verificationActions.push(item.text);
522
+ }
523
+ verificationCommands.push(...extractVerificationCommands(item.text));
524
+ const executionIntent = normalizeExecutionIntent(item.text);
525
+ if (executionIntent) {
526
+ executionHints.push(executionIntent);
527
+ }
528
+ }
529
+
530
+ const mergedFileReferences = unique([...targetFiles, ...rawFileReferences]);
531
+ const titleExecutionIntent = normalizeExecutionIntent(section.title);
532
+ if (titleExecutionIntent) {
533
+ executionHints.push(titleExecutionIntent);
534
+ }
535
+
536
+ const joinedContent = [section.title, ...lines, ...section.checklistItems.map((item) => item.text)].join("\n");
537
+ return {
538
+ ...section,
539
+ targetFiles: unique(targetFiles),
540
+ fileReferences: mergedFileReferences,
541
+ verificationActions: unique(verificationActions.filter(Boolean)),
542
+ verificationCommands: unique(verificationCommands),
543
+ executionIntent: unique(executionHints),
544
+ reviewIntent: hasReviewIntent(joinedContent),
545
+ testingIntent: hasTestingIntent(joinedContent),
546
+ codeChangeLikely: looksLikeCodeChange(joinedContent),
547
+ placeholderItems: unique(placeholderItems.filter(Boolean))
548
+ };
549
+ }
550
+
353
551
  function parseTasksArtifact(text) {
354
552
  const lines = String(text || "").replace(/\r\n?/g, "\n").split("\n");
355
553
  const taskGroups = [];
356
554
  const checklistItems = [];
357
555
  const checkpointItems = [];
358
556
  const sections = [];
557
+ const markers = parseDisciplineMarkers(text);
359
558
  let currentSection = null;
360
559
 
361
- for (const line of lines) {
560
+ for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {
561
+ const line = lines[lineIndex];
362
562
  const groupMatch = line.match(/^\s{0,3}##\s+(\d+(?:\.\d+)*)\.\s+(.+)$/);
363
563
  if (groupMatch) {
364
564
  const group = {
365
565
  id: groupMatch[1],
366
- title: String(groupMatch[2] || "").trim()
566
+ title: String(groupMatch[2] || "").trim(),
567
+ startLine: lineIndex + 1
367
568
  };
368
569
  taskGroups.push(group);
369
570
  if (currentSection) {
370
- sections.push(currentSection);
571
+ currentSection.endLine = lineIndex;
572
+ sections.push(analyzeTaskGroup(currentSection));
371
573
  }
372
574
  currentSection = {
373
575
  id: group.id,
374
576
  title: group.title,
375
- checklistItems: []
577
+ startLine: lineIndex + 1,
578
+ endLine: lineIndex + 1,
579
+ checklistItems: [],
580
+ lines: []
376
581
  };
377
582
  continue;
378
583
  }
379
584
 
585
+ if (currentSection) {
586
+ currentSection.lines.push(line);
587
+ currentSection.endLine = lineIndex + 1;
588
+ }
589
+
380
590
  const checklistMatch = line.match(/^\s*-\s*\[([ xX])\]\s+(.+)$/);
381
591
  if (checklistMatch) {
382
592
  const checked = String(checklistMatch[1] || "").toLowerCase() === "x";
@@ -384,33 +594,60 @@ function parseTasksArtifact(text) {
384
594
  if (!textValue) {
385
595
  continue;
386
596
  }
387
- checklistItems.push({
597
+ const item = {
388
598
  checked,
389
- text: textValue
390
- });
599
+ text: textValue,
600
+ line: lineIndex + 1
601
+ };
602
+ checklistItems.push(item);
391
603
  if (currentSection) {
392
- currentSection.checklistItems.push({
393
- checked,
394
- text: textValue
395
- });
604
+ currentSection.checklistItems.push(item);
396
605
  }
397
606
  if (/checkpoint/i.test(textValue)) {
398
- checkpointItems.push({
399
- checked,
400
- text: textValue
401
- });
607
+ checkpointItems.push(item);
402
608
  }
403
609
  }
404
610
  }
405
611
  if (currentSection) {
406
- sections.push(currentSection);
612
+ sections.push(analyzeTaskGroup(currentSection));
407
613
  }
408
614
 
615
+ const sectionById = new Map(sections.map((section) => [section.id, section]));
616
+ const normalizedTaskGroups = taskGroups.map((group) => {
617
+ const section = sectionById.get(group.id);
618
+ if (!section) {
619
+ return group;
620
+ }
621
+ return {
622
+ id: group.id,
623
+ title: group.title,
624
+ startLine: group.startLine,
625
+ endLine: section.endLine,
626
+ targetFiles: section.targetFiles,
627
+ fileReferences: section.fileReferences,
628
+ verificationActions: section.verificationActions,
629
+ verificationCommands: section.verificationCommands,
630
+ executionIntent: section.executionIntent,
631
+ reviewIntent: section.reviewIntent,
632
+ testingIntent: section.testingIntent,
633
+ codeChangeLikely: section.codeChangeLikely,
634
+ placeholderItems: section.placeholderItems,
635
+ checklistItems: section.checklistItems
636
+ };
637
+ });
638
+
409
639
  return {
410
- taskGroups,
640
+ taskGroups: normalizedTaskGroups,
411
641
  checklistItems,
412
642
  checkpointItems,
413
643
  sections,
644
+ markers,
645
+ markerSummary: {
646
+ hasDesignApproval: Boolean(markers.markers[DISCIPLINE_MARKER_NAMES.designApproval]),
647
+ hasPlanSelfReview: Boolean(markers.markers[DISCIPLINE_MARKER_NAMES.planSelfReview]),
648
+ hasOperatorReviewAck: Boolean(markers.markers[DISCIPLINE_MARKER_NAMES.operatorReviewAck]),
649
+ malformedCount: markers.malformed.length
650
+ },
414
651
  text: String(text || "")
415
652
  };
416
653
  }