@fitlab-ai/agent-infra 0.11.3 → 0.11.5
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 +17 -8
- package/README.zh-CN.md +17 -8
- package/dist/bin/internal-cli.js +1 -6
- package/dist/lib/decide.js +4 -41
- package/dist/lib/git/branch-worktree.js +45 -0
- package/dist/lib/internal/cli-route-inventory.js +1 -4
- package/dist/lib/internal/task-activity.js +7 -40
- package/dist/lib/internal/task-artifact.js +1 -1
- package/dist/lib/internal/task-event.js +4 -37
- package/dist/lib/internal/task-finalization.js +48 -1
- package/dist/lib/internal/task-ledger.js +14 -44
- package/dist/lib/internal/task-lifecycle.js +1 -1
- package/dist/lib/internal/task-operation-registry.js +2 -4
- package/dist/lib/internal/task-review.js +5 -37
- package/dist/lib/internal/task-verify.js +3 -47
- package/dist/lib/internal/task-warning.js +4 -40
- package/dist/lib/platform/completion-backfill.js +63 -17
- package/dist/lib/platform/pr-summary.js +4 -23
- package/dist/lib/platform/verification-sync.js +12 -49
- package/dist/lib/sandbox/control/client.js +4 -1
- package/dist/lib/sandbox/control/executor.js +12 -4
- package/dist/lib/sandbox/control/protocol.js +2 -1
- package/dist/lib/sandbox/control/server.js +26 -143
- package/dist/lib/sandbox/removal.js +15 -8
- package/dist/lib/sandbox/task-cleanup.js +13 -20
- package/dist/lib/task/activity-intent.js +1 -2
- package/dist/lib/task/artifact-command.js +11 -4
- package/dist/lib/task/artifact-lifecycle.js +217 -85
- package/dist/lib/task/artifact-operations.js +37 -28
- package/dist/lib/task/artifact-receipts.js +4 -1
- package/dist/lib/task/artifact-schema.js +8 -7
- package/dist/lib/task/artifact-selection.js +25 -0
- package/dist/lib/task/capabilities.js +158 -39
- package/dist/lib/task/commands/status.js +9 -2
- package/dist/lib/task/control-authority.js +12 -168
- package/dist/lib/task/decision-intents.js +14 -4
- package/dist/lib/task/delivery.js +1 -43
- package/dist/lib/task/events.js +68 -73
- package/dist/lib/task/finalization-handoff.js +92 -0
- package/dist/lib/task/finalization.js +423 -154
- package/dist/lib/task/intermediate-cleanup.js +9 -148
- package/dist/lib/task/ledger-intents.js +159 -20
- package/dist/lib/task/lifecycle-path.js +75 -0
- package/dist/lib/task/lifecycle.js +60 -11
- package/dist/lib/task/local-artifact-finalization.js +13 -47
- package/dist/lib/task/orchestration.js +17 -7
- package/dist/lib/task/review-command.js +1 -2
- package/dist/lib/task/review-finalization.js +4 -18
- package/dist/lib/task/rework-intent.js +70 -4
- package/dist/lib/task/snapshot.js +6 -6
- package/dist/lib/task/verification-engine.js +25 -30
- package/dist/lib/task/verification.js +2 -13
- package/dist/lib/task/workflow-warning-intents.js +23 -10
- package/dist/lib/task/workspace-snapshot.js +7 -2
- package/dist/lib/task/write.js +1 -2
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/decide.ts +5 -35
- package/lib/git/branch-worktree.ts +41 -0
- package/lib/internal/cli-route-inventory.ts +1 -3
- package/lib/internal/task-activity.ts +6 -34
- package/lib/internal/task-artifact.ts +1 -1
- package/lib/internal/task-event.ts +4 -37
- package/lib/internal/task-finalization.ts +47 -1
- package/lib/internal/task-ledger.ts +15 -41
- package/lib/internal/task-lifecycle.ts +2 -2
- package/lib/internal/task-operation-registry.ts +2 -4
- package/lib/internal/task-review.ts +5 -31
- package/lib/internal/task-verify.ts +3 -39
- package/lib/internal/task-warning.ts +3 -32
- package/lib/platform/completion-backfill.ts +78 -16
- package/lib/platform/pr-summary.ts +4 -25
- package/lib/platform/verification-sync.ts +13 -53
- package/lib/sandbox/control/client.ts +5 -1
- package/lib/sandbox/control/executor.ts +13 -4
- package/lib/sandbox/control/protocol.ts +3 -2
- package/lib/sandbox/control/server.ts +21 -146
- package/lib/sandbox/removal.ts +23 -7
- package/lib/sandbox/task-cleanup.ts +12 -20
- package/lib/task/activity-intent.ts +1 -2
- package/lib/task/artifact-command.ts +14 -4
- package/lib/task/artifact-lifecycle.ts +236 -85
- package/lib/task/artifact-operations.ts +59 -28
- package/lib/task/artifact-receipts.ts +4 -1
- package/lib/task/artifact-schema.ts +10 -7
- package/lib/task/artifact-selection.ts +53 -0
- package/lib/task/capabilities.ts +154 -40
- package/lib/task/commands/status.ts +11 -2
- package/lib/task/control-authority.ts +17 -189
- package/lib/task/decision-intents.ts +11 -4
- package/lib/task/delivery.ts +1 -40
- package/lib/task/events.ts +73 -74
- package/lib/task/finalization-handoff.ts +104 -0
- package/lib/task/finalization.ts +388 -122
- package/lib/task/intermediate-cleanup.ts +9 -125
- package/lib/task/ledger-intents.ts +157 -20
- package/lib/task/lifecycle-path.ts +98 -0
- package/lib/task/lifecycle.ts +58 -13
- package/lib/task/local-artifact-finalization.ts +14 -56
- package/lib/task/orchestration.ts +26 -8
- package/lib/task/review-command.ts +2 -5
- package/lib/task/review-finalization.ts +4 -20
- package/lib/task/rework-intent.ts +75 -5
- package/lib/task/snapshot.ts +6 -6
- package/lib/task/verification-engine.ts +29 -30
- package/lib/task/verification.ts +3 -16
- package/lib/task/workflow-warning-intents.ts +20 -10
- package/lib/task/workspace-snapshot.ts +3 -2
- package/lib/task/write.ts +1 -4
- package/package.json +1 -1
- package/templates/.agents/rules/review-handshake.en.md +4 -4
- package/templates/.agents/rules/review-handshake.zh-CN.md +4 -4
- package/templates/.agents/skills/analyze-task/SKILL.en.md +21 -5
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +21 -5
- package/templates/.agents/skills/code-task/SKILL.en.md +14 -8
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +18 -13
- package/templates/.agents/skills/code-task/reference/code-rules.en.md +2 -2
- package/templates/.agents/skills/code-task/reference/code-rules.zh-CN.md +2 -2
- package/templates/.agents/skills/code-task/reference/dual-mode.en.md +4 -4
- package/templates/.agents/skills/code-task/reference/dual-mode.zh-CN.md +4 -4
- package/templates/.agents/skills/code-task/reference/report-template.en.md +7 -7
- package/templates/.agents/skills/code-task/reference/report-template.zh-CN.md +7 -7
- package/templates/.agents/skills/complete-manual-validation/config/verify.en.json +2 -2
- package/templates/.agents/skills/complete-manual-validation/config/verify.zh-CN.json +2 -2
- package/templates/.agents/skills/complete-manual-validation/reference/report-template.en.md +1 -3
- package/templates/.agents/skills/complete-manual-validation/reference/report-template.zh-CN.md +1 -3
- package/templates/.agents/skills/complete-task/SKILL.en.md +2 -2
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/complete-task/config/verify.en.json +2 -2
- package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +2 -2
- package/templates/.agents/skills/plan-task/SKILL.en.md +13 -10
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +13 -10
- package/templates/.agents/skills/review-analysis/SKILL.en.md +5 -1
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +5 -1
- package/templates/.agents/skills/review-analysis/reference/report-template.en.md +3 -3
- package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +3 -3
- package/templates/.agents/skills/review-code/SKILL.en.md +5 -1
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +5 -1
- package/templates/.agents/skills/review-code/reference/report-template.en.md +3 -3
- package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +3 -3
- package/templates/.agents/skills/review-plan/SKILL.en.md +5 -1
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +5 -1
- package/templates/.agents/skills/review-plan/reference/report-template.en.md +3 -3
- package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +3 -3
- package/templates/.agents/skills/review-pr/config/verify.en.json +3 -1
- package/templates/.agents/skills/review-pr/config/verify.zh-CN.json +3 -1
- package/templates/.agents/skills/review-pr/reference/report-template.en.md +3 -3
- package/templates/.agents/skills/review-pr/reference/report-template.zh-CN.md +3 -3
- package/templates/.agents/skills/run-manual-validation/config/verify.en.json +2 -2
- package/templates/.agents/skills/run-manual-validation/config/verify.zh-CN.json +2 -2
- package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +2 -2
- package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +2 -2
- package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +18 -2
- package/templates/.agents/templates/task.en.md +2 -2
- package/templates/.agents/templates/task.zh-CN.md +2 -2
- package/templates/.agents/workflows/bug-fix.en.yaml +11 -6
- package/templates/.agents/workflows/bug-fix.zh-CN.yaml +11 -6
- package/templates/.agents/workflows/feature-development.en.yaml +11 -6
- package/templates/.agents/workflows/feature-development.zh-CN.yaml +11 -6
- package/templates/.agents/workflows/refactoring.en.yaml +11 -6
- package/templates/.agents/workflows/refactoring.zh-CN.yaml +11 -6
- package/dist/lib/internal/task-override.js +0 -138
- package/dist/lib/sandbox/control/finalization-response.js +0 -33
- package/dist/lib/task/artifact-recovery.js +0 -476
- package/dist/lib/task/artifact-repair-intent.js +0 -181
- package/dist/lib/task/guard-override.js +0 -31
- package/dist/lib/task/human-override.js +0 -1259
- package/lib/internal/task-override.ts +0 -144
- package/lib/sandbox/control/finalization-response.ts +0 -35
- package/lib/task/artifact-recovery.ts +0 -572
- package/lib/task/artifact-repair-intent.ts +0 -227
- package/lib/task/guard-override.ts +0 -50
- package/lib/task/human-override.ts +0 -1522
|
@@ -43,9 +43,6 @@ const lifecycleProducerCatalog = lifecycleFailureCatalog.map((code) => ({
|
|
|
43
43
|
guardId: 'G-03',
|
|
44
44
|
code
|
|
45
45
|
}));
|
|
46
|
-
function allowsManualOverride(options, code) {
|
|
47
|
-
return options.manualOverride?.failureId === `lifecycle.apply:${code}`;
|
|
48
|
-
}
|
|
49
46
|
const STEPS = ['task-written', 'directory-moved', 'registry-committed'];
|
|
50
47
|
const DEFAULT_IO = {
|
|
51
48
|
renameSync: (source, target) => fs.renameSync(source, target),
|
|
@@ -320,6 +317,49 @@ export function readLifecycleJournalEvidence(repoRoot, taskId) {
|
|
|
320
317
|
return { exists: true, completedSteps: [], failure: 'SANDBOX_CONTROL_LIFECYCLE_JOURNAL_INVALID' };
|
|
321
318
|
}
|
|
322
319
|
}
|
|
320
|
+
export function inspectTaskLifecycleProgress(repoRoot, taskId, agent) {
|
|
321
|
+
try {
|
|
322
|
+
const hot = locateHotTaskDirs(repoRoot, taskId);
|
|
323
|
+
if (hot.length !== 1 || (hot[0].state !== 'active' && hot[0].state !== 'completed'))
|
|
324
|
+
return 'unknown';
|
|
325
|
+
const { state, taskDir } = hot[0];
|
|
326
|
+
const content = fs.readFileSync(path.join(taskDir, 'task.md'), 'utf8');
|
|
327
|
+
const frontmatter = parseTypedTaskFrontmatter(content);
|
|
328
|
+
if (frontmatter.id !== taskId)
|
|
329
|
+
return 'unknown';
|
|
330
|
+
const journalPath = path.join(taskDir, '.task-lifecycle.json');
|
|
331
|
+
if (!fs.existsSync(journalPath)) {
|
|
332
|
+
if (state === 'active')
|
|
333
|
+
return frontmatter.status === 'active' ? 'not-started' : 'unknown';
|
|
334
|
+
return matchingCompletion(content, { taskRef: taskId, intent: 'complete', agent })
|
|
335
|
+
? 'started-recoverable' : 'unknown';
|
|
336
|
+
}
|
|
337
|
+
const journal = parseJournal(fs.readFileSync(journalPath, 'utf8'));
|
|
338
|
+
const request = { taskRef: taskId, intent: 'complete', agent };
|
|
339
|
+
if (journal.taskId !== taskId || journal.intent !== 'complete'
|
|
340
|
+
|| journal.intentDigest !== intentIdentity(request, taskId)
|
|
341
|
+
|| journal.sourceState !== 'active' || journal.targetState !== 'completed')
|
|
342
|
+
return 'unknown';
|
|
343
|
+
if (journal.completedSteps.some((step, index) => STEPS[index] !== step))
|
|
344
|
+
return 'unknown';
|
|
345
|
+
const directoryMoved = journal.completedSteps.includes('directory-moved');
|
|
346
|
+
const registryCommitted = journal.completedSteps.includes('registry-committed');
|
|
347
|
+
const hasShortId = loadShortIdByTaskId(repoRoot).has(taskId);
|
|
348
|
+
if (state === 'active' && (directoryMoved || !hasShortId))
|
|
349
|
+
return 'unknown';
|
|
350
|
+
if (state === 'completed' && (!journal.completedSteps.includes('task-written')
|
|
351
|
+
|| !matchingCompletion(content, request) || (registryCommitted && hasShortId)))
|
|
352
|
+
return 'unknown';
|
|
353
|
+
if (frontmatter.status !== state
|
|
354
|
+
&& !(state === 'active' && frontmatter.status === 'completed' && matchingCompletion(content, request))) {
|
|
355
|
+
return 'unknown';
|
|
356
|
+
}
|
|
357
|
+
return 'started-recoverable';
|
|
358
|
+
}
|
|
359
|
+
catch {
|
|
360
|
+
return 'unknown';
|
|
361
|
+
}
|
|
362
|
+
}
|
|
323
363
|
function directoryManifest(root) {
|
|
324
364
|
const entries = [];
|
|
325
365
|
const visit = (current, relative = '') => {
|
|
@@ -440,7 +480,7 @@ function applyTaskLifecycle(requestInput, options = {}) {
|
|
|
440
480
|
}
|
|
441
481
|
return failed(request, { code: 'LIFECYCLE_INTENT_CONFLICT', message: `task ${taskId} is already ${spec.target} with a different lifecycle intent` }, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath });
|
|
442
482
|
}
|
|
443
|
-
if (!spec.sources.includes(sourceState) && !existingJournal
|
|
483
|
+
if (!spec.sources.includes(sourceState) && !existingJournal) {
|
|
444
484
|
return failed(request, { code: 'LIFECYCLE_SOURCE_INVALID', message: `${request.intent} is not allowed from ${sourceState}` }, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath });
|
|
445
485
|
}
|
|
446
486
|
if (sourcePath !== targetPath && fs.existsSync(targetPath) && !existingJournal) {
|
|
@@ -456,7 +496,7 @@ function applyTaskLifecycle(requestInput, options = {}) {
|
|
|
456
496
|
}
|
|
457
497
|
if (frontmatter.id !== taskId)
|
|
458
498
|
return failed(request, { code: 'LIFECYCLE_IDENTITY_INVALID', message: 'task.md id does not match its lifecycle task id' }, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath });
|
|
459
|
-
if (!locateActivityLog(content)
|
|
499
|
+
if (!locateActivityLog(content))
|
|
460
500
|
return failed(request, { code: 'LIFECYCLE_LOG_MISSING', message: 'task has no unique Activity Log section' }, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath });
|
|
461
501
|
if (spec.target === 'active') {
|
|
462
502
|
const contract = validateCurrentTaskContract(content);
|
|
@@ -472,10 +512,10 @@ function applyTaskLifecycle(requestInput, options = {}) {
|
|
|
472
512
|
}
|
|
473
513
|
const registry = JSON.parse(registryRequest.output || '{"version":1,"ids":{}}');
|
|
474
514
|
const registeredKey = Object.entries(registry.ids).find(([, candidate]) => candidate === taskId)?.[0];
|
|
475
|
-
if (spec.registry === 'release' && sourceState === 'active' && !registeredKey
|
|
515
|
+
if (spec.registry === 'release' && sourceState === 'active' && !registeredKey) {
|
|
476
516
|
return failed(request, { code: 'LIFECYCLE_SHORT_ID_PRECONDITION', message: 'active source has no short-id registry entry' }, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath });
|
|
477
517
|
}
|
|
478
|
-
if (spec.registry === 'alloc' && !registeredKey && Object.keys(registry.ids).length >= 10 ** configuredShortIdLength(repoRoot) - 1
|
|
518
|
+
if (spec.registry === 'alloc' && !registeredKey && Object.keys(registry.ids).length >= 10 ** configuredShortIdLength(repoRoot) - 1) {
|
|
479
519
|
return failed(request, { code: 'SHORT_ID_CAPACITY_EXCEEDED', message: 'short-id registry has no free slot' }, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath });
|
|
480
520
|
}
|
|
481
521
|
const digest = intentIdentity(request, taskId);
|
|
@@ -499,8 +539,7 @@ function applyTaskLifecycle(requestInput, options = {}) {
|
|
|
499
539
|
return failed(request, { code: 'LIFECYCLE_JOURNAL_INVALID', message: 'existing lifecycle journal has no durable failure evidence for a read-only probe' }, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath, journalPath: initialJournalPath });
|
|
500
540
|
return failed(request, journal.failure, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath, journalPath: initialJournalPath, completedSteps: journal.completedSteps, pendingSteps: STEPS.filter((step) => !journal.completedSteps.includes(step)) });
|
|
501
541
|
}
|
|
502
|
-
|
|
503
|
-
if (journal.intentDigest !== digest && !overrideResumesRecordedFailure)
|
|
542
|
+
if (journal.intentDigest !== digest)
|
|
504
543
|
return failed(request, { code: 'LIFECYCLE_INTENT_CONFLICT', message: 'an in-progress lifecycle journal belongs to a different request' }, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath, journalPath: initialJournalPath });
|
|
505
544
|
}
|
|
506
545
|
else {
|
|
@@ -516,7 +555,7 @@ function applyTaskLifecycle(requestInput, options = {}) {
|
|
|
516
555
|
}
|
|
517
556
|
journal = { version: 1, taskId, intent: request.intent, intentDigest: digest, sourceState, targetState: spec.target, metadata, completedSteps: [] };
|
|
518
557
|
if (request.dryRun) {
|
|
519
|
-
const plannedMutations = mutationsFor(request, content, metadata, restoredFiles,
|
|
558
|
+
const plannedMutations = mutationsFor(request, content, metadata, restoredFiles, false);
|
|
520
559
|
if ('code' in plannedMutations)
|
|
521
560
|
return failed(request, plannedMutations, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath });
|
|
522
561
|
const taskPlan = writeTask({ taskRef: taskId, expectedState: sourceState === 'blocked' ? 'blocked' : 'active', mutations: plannedMutations, dryRun: true }, {
|
|
@@ -558,7 +597,7 @@ function applyTaskLifecycle(requestInput, options = {}) {
|
|
|
558
597
|
completed.add('task-written');
|
|
559
598
|
if (!completed.has('task-written')) {
|
|
560
599
|
const current = io.readFileSync(path.join(sourcePath, 'task.md'));
|
|
561
|
-
const mutations = mutationsFor(request, current, journal.metadata, restoredFiles,
|
|
600
|
+
const mutations = mutationsFor(request, current, journal.metadata, restoredFiles, false);
|
|
562
601
|
if ('code' in mutations)
|
|
563
602
|
return failed(request, mutations, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath, journalPath, completedSteps: [...completed], changed: completed.size > 0 });
|
|
564
603
|
const writeResult = writeTask({ taskRef: taskId, expectedState: sourceState === 'blocked' ? 'blocked' : 'active', mutations }, {
|
|
@@ -578,6 +617,16 @@ function applyTaskLifecycle(requestInput, options = {}) {
|
|
|
578
617
|
return failed(request, { code: 'LIFECYCLE_JOURNAL_WRITE_FAILED', message: String(error) }, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath, journalPath, task: { operations: taskOperations }, changed: true, completedSteps: [...completed], timestamp: journal.metadata.timestamp, agentInfraVersion: journal.metadata.agentInfraVersion });
|
|
579
618
|
}
|
|
580
619
|
}
|
|
620
|
+
if (options.prepareOnly && !completed.has('directory-moved')) {
|
|
621
|
+
return {
|
|
622
|
+
...failed(request, { code: '', message: '' }), status: 'applied', changed: true, error: null,
|
|
623
|
+
taskId, sourceState, targetState: spec.target, sourcePath, targetPath,
|
|
624
|
+
task: { operations: taskOperations }, directory: { effect: 'unchanged', changed: false },
|
|
625
|
+
shortId: { effect: 'unchanged', shortId: null, changed: false },
|
|
626
|
+
timestamp: journal.metadata.timestamp, agentInfraVersion: journal.metadata.agentInfraVersion,
|
|
627
|
+
journalPath, completedSteps: [...completed], pendingSteps: STEPS.filter((step) => !completed.has(step))
|
|
628
|
+
};
|
|
629
|
+
}
|
|
581
630
|
if (fs.existsSync(targetPath) && !fs.existsSync(sourcePath))
|
|
582
631
|
completed.add('directory-moved');
|
|
583
632
|
if (!completed.has('directory-moved')) {
|
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { inspectDecisionDetailDuplicates } from './decision-details.js';
|
|
4
|
-
import { scanVisibleMarkdown } from './markdown.js';
|
|
5
4
|
import { parseArtifactName } from './artifact-name.js';
|
|
6
|
-
import {
|
|
5
|
+
import { parseLifecyclePathDecision } from './lifecycle-path.js';
|
|
7
6
|
import { resolveTaskRef } from './resolve-ref.js';
|
|
8
|
-
import {
|
|
9
|
-
import { canonicalSemanticDigest, inspectArtifactPatterns, inspectArtifactStructure, sha256Content } from './artifact-operations.js';
|
|
7
|
+
import { canonicalSemanticDigest, inspectArtifactContract, sha256Content } from './artifact-operations.js';
|
|
10
8
|
import { getArtifactSchema } from './artifact-schema.js';
|
|
11
9
|
const LOCAL_ARTIFACT_REQUIRED_SECTIONS = {
|
|
12
10
|
analysis: getArtifactSchema('analysis').sections.map((section) => section.headings.zh),
|
|
@@ -18,59 +16,29 @@ function localDiagnosticCode(code) {
|
|
|
18
16
|
ARTIFACT_EMPTY: 'LOCAL_ARTIFACT_EMPTY',
|
|
19
17
|
ARTIFACT_MISSING_SECTION: 'LOCAL_ARTIFACT_MISSING_SECTION',
|
|
20
18
|
ARTIFACT_DUPLICATE_SECTION: 'LOCAL_ARTIFACT_DUPLICATE_SECTION',
|
|
21
|
-
ARTIFACT_HEADING_TRAILING_PUNCTUATION: 'LOCAL_SECTION_HEADING_TRAILING_PUNCTUATION'
|
|
19
|
+
ARTIFACT_HEADING_TRAILING_PUNCTUATION: 'LOCAL_SECTION_HEADING_TRAILING_PUNCTUATION',
|
|
20
|
+
ARTIFACT_STATE_CHECK_PATTERN_MISSING: 'LOCAL_STATUS_COMMAND_MISSING',
|
|
21
|
+
ARTIFACT_REQUIRED_PATTERN_MISSING: 'LOCAL_REQUIRED_PATTERN_MISSING'
|
|
22
22
|
};
|
|
23
23
|
return map[code] ?? 'LOCAL_STRUCTURAL_INVALID';
|
|
24
24
|
}
|
|
25
25
|
function localDiagnostic(item) {
|
|
26
26
|
return { code: localDiagnosticCode(item.code), message: item.message, line: item.line };
|
|
27
27
|
}
|
|
28
|
-
function isStatusPattern(pattern) {
|
|
29
|
-
return pattern === '^\\$ ';
|
|
30
|
-
}
|
|
31
28
|
function validateLocalArtifact(content, options) {
|
|
32
29
|
const schema = getArtifactSchema(options.family);
|
|
33
30
|
const diagnostics = [];
|
|
34
|
-
const
|
|
35
|
-
const
|
|
36
|
-
for (const item of structure.diagnostics)
|
|
31
|
+
const inspection = inspectArtifactContract(content, schema, { scopeStateCheckSection: true });
|
|
32
|
+
for (const item of inspection.diagnostics)
|
|
37
33
|
diagnostics.push(localDiagnostic(item));
|
|
38
|
-
const statusSection = schema.sections.find((section) => section.id === 'state-check');
|
|
39
|
-
if (statusSection) {
|
|
40
|
-
const statusHeading = scanned.headings.find((heading) => (heading.level === 2 && [statusSection.headings.zh, statusSection.headings.en].some((name) => (heading.text === name || heading.text === `${name}:` || heading.text === `${name}:`))));
|
|
41
|
-
if (statusHeading) {
|
|
42
|
-
const next = scanned.headings.find((candidate) => candidate.start > statusHeading.start && candidate.level <= 2);
|
|
43
|
-
const body = content.slice(statusHeading.end, next?.start ?? content.length);
|
|
44
|
-
for (const pattern of schema.requiredPatterns.filter(isStatusPattern)) {
|
|
45
|
-
if (!new RegExp(pattern, 'm').test(body)) {
|
|
46
|
-
diagnostics.push({ code: 'LOCAL_STATUS_COMMAND_MISSING', message: `status section '${statusHeading.text}' is missing required command output`, line: content.slice(0, statusHeading.start).split('\n').length });
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
const patternInspection = inspectArtifactPatterns(content, {
|
|
52
|
-
...schema,
|
|
53
|
-
requiredPatterns: schema.requiredPatterns.filter((item) => !isStatusPattern(item))
|
|
54
|
-
});
|
|
55
|
-
for (const item of patternInspection.diagnostics)
|
|
56
|
-
diagnostics.push({ code: 'LOCAL_REQUIRED_PATTERN_MISSING', message: item.message, line: null });
|
|
57
34
|
const decisionDetails = inspectDecisionDetailDuplicates(content);
|
|
58
35
|
if (!decisionDetails.ok)
|
|
59
36
|
diagnostics.push({ code: 'LOCAL_DECISION_DETAIL_DUPLICATE', message: decisionDetails.message, line: null });
|
|
60
|
-
if (options.
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
if (expected && !expected.ok)
|
|
66
|
-
diagnostics.push({ code: 'LOCAL_QUALIFICATION_AUDIT_INVALID', message: `${expected.code}: ${expected.message}`, line: null });
|
|
67
|
-
const qualification = validateQualificationAudit(options.taskContent, content, {
|
|
68
|
-
family: options.family,
|
|
69
|
-
artifact: options.artifact,
|
|
70
|
-
expectedUpstreamRelations: expected?.ok ? expected.relations : undefined
|
|
71
|
-
});
|
|
72
|
-
if (!qualification.ok)
|
|
73
|
-
diagnostics.push({ code: 'LOCAL_QUALIFICATION_AUDIT_INVALID', message: `${qualification.code}: ${qualification.message}`, line: null });
|
|
37
|
+
if (options.family === 'analysis') {
|
|
38
|
+
const flowDecision = parseLifecyclePathDecision(content);
|
|
39
|
+
if (flowDecision.status !== 'valid') {
|
|
40
|
+
diagnostics.push({ code: 'LOCAL_FLOW_DECISION_INVALID', message: flowDecision.message, line: null });
|
|
41
|
+
}
|
|
74
42
|
}
|
|
75
43
|
return {
|
|
76
44
|
ok: diagnostics.length === 0,
|
|
@@ -98,10 +66,8 @@ function prepareLocalArtifact(request, candidate) {
|
|
|
98
66
|
const parsed = parseArtifactName(request.artifact);
|
|
99
67
|
if (!parsed || parsed.family !== request.family)
|
|
100
68
|
return failed('ARTIFACT_IDENTITY_INVALID', `artifact '${request.artifact}' does not match ${request.family}`);
|
|
101
|
-
let taskContent;
|
|
102
69
|
let content;
|
|
103
70
|
try {
|
|
104
|
-
taskContent = fs.readFileSync(resolved.taskMdPath, 'utf8');
|
|
105
71
|
const artifactPath = path.join(resolved.taskDir, request.artifact);
|
|
106
72
|
if (!fs.statSync(artifactPath).isFile())
|
|
107
73
|
return failed('ARTIFACT_NOT_REGULAR', `artifact '${request.artifact}' is not a regular file`);
|
|
@@ -114,7 +80,7 @@ function prepareLocalArtifact(request, candidate) {
|
|
|
114
80
|
return failed('ARTIFACT_IDENTITY_INVALID', 'artifact finalization requires an active task');
|
|
115
81
|
}
|
|
116
82
|
const validation = validateLocalArtifact(content, {
|
|
117
|
-
family: request.family, requiredSections: request.requiredSections
|
|
83
|
+
family: request.family, requiredSections: request.requiredSections
|
|
118
84
|
});
|
|
119
85
|
const artifactSha256 = sha256Content(content);
|
|
120
86
|
const result = failedFinalization(request, {
|
|
@@ -22,6 +22,7 @@ import { buildLifecycleFacts, canStart, recommendNext } from './capabilities.js'
|
|
|
22
22
|
import { hasOpenLifecycleExecution } from './activity-log.js';
|
|
23
23
|
import { reconcileTaskInvalidation } from './invalidation-command.js';
|
|
24
24
|
import { normalizeAgentToken } from '../agent-clients/tokens.js';
|
|
25
|
+
import { resolveArtifactContext } from './artifact-lifecycle.js';
|
|
25
26
|
function supportsLifecycleDelegation(client) {
|
|
26
27
|
return getAgentClientCapability(client, 'subagents').level !== 'unsupported'
|
|
27
28
|
&& getAgentClientCapability(client, 'orchestration').level !== 'unsupported'
|
|
@@ -479,17 +480,13 @@ function validateSavedCurrentDiffBase(repoRoot, metadata, taskDir, reviewedHead)
|
|
|
479
480
|
function routeFromFacts(facts) {
|
|
480
481
|
const recommendation = recommendNext(facts);
|
|
481
482
|
if (!recommendation.action)
|
|
482
|
-
return { completion: true };
|
|
483
|
+
return recommendation.reasonCode === 'LIFECYCLE_REVIEWED' ? { completion: true } : null;
|
|
483
484
|
const action = recommendation.action;
|
|
484
485
|
if (action === 'manual-validation' || action === 'validation-run')
|
|
485
486
|
return null;
|
|
486
|
-
const family = action === 'analysis' ? 'analysis' : action;
|
|
487
|
-
const round = maxArtifactRound([
|
|
488
|
-
...(facts.artifacts[family] ?? []), ...(facts.staleArtifacts?.[family] ?? [])
|
|
489
|
-
], family) + 1;
|
|
490
487
|
const command = action === 'analysis' ? 'analyze-task' : action === 'plan' ? 'plan-task' : action === 'code' ? 'code-task' : action;
|
|
491
488
|
const role = action.startsWith('review-') ? 'reviewer' : 'executor';
|
|
492
|
-
return { action: command, role, stage: action
|
|
489
|
+
return { action: command, role, stage: action };
|
|
493
490
|
}
|
|
494
491
|
function routeOrchestration(taskRef, options = {}) {
|
|
495
492
|
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
@@ -619,8 +616,19 @@ function routeOrchestration(taskRef, options = {}) {
|
|
|
619
616
|
return { status: 'completed', changed: true, taskId: resolved.taskId, run: completed, next: null, error: null };
|
|
620
617
|
}
|
|
621
618
|
const action = routed.stage;
|
|
619
|
+
const family = action === 'analysis' ? 'analysis' : action;
|
|
620
|
+
const artifactContext = resolveArtifactContext(taskRef, family, {
|
|
621
|
+
repoRoot: resolved.repoRoot
|
|
622
|
+
});
|
|
623
|
+
if (artifactContext.status !== 'ready' || !artifactContext.selection) {
|
|
624
|
+
return failed(artifactContext.error?.code ?? 'ORCHESTRATION_ARTIFACT_SELECTION_FAILED', artifactContext.error?.message ?? 'cannot determine artifact selection', resolved.taskId);
|
|
625
|
+
}
|
|
626
|
+
if (artifactContext.selection.disposition === 'reuse') {
|
|
627
|
+
return failed('ORCHESTRATION_REUSE_STALLED', `lifecycle still routes to ${action} after reusing ${artifactContext.selection.artifact.name}`, resolved.taskId);
|
|
628
|
+
}
|
|
629
|
+
const selected = artifactContext.selection.artifact;
|
|
622
630
|
const capability = canStart(action, facts.facts, {
|
|
623
|
-
initiator: 'orchestrator', requestId: `orchestration:${resolved.taskId}:${routed.stage}:${
|
|
631
|
+
initiator: 'orchestrator', requestId: `orchestration:${resolved.taskId}:${routed.stage}:${selected.round}`,
|
|
624
632
|
requestedAction: action, reasonCode: 'user-request', explicitRequest: true
|
|
625
633
|
});
|
|
626
634
|
if (!capability.allowed)
|
|
@@ -628,6 +636,8 @@ function routeOrchestration(taskRef, options = {}) {
|
|
|
628
636
|
const policy = run ? rolePolicy(run, routed.role) : null;
|
|
629
637
|
const next = {
|
|
630
638
|
...routed,
|
|
639
|
+
round: selected.round,
|
|
640
|
+
artifact: selected.name,
|
|
631
641
|
requestedModel: policy?.model ?? null,
|
|
632
642
|
requestedReasoningEffort: policy?.reasoningEffort ?? null
|
|
633
643
|
};
|
|
@@ -3,8 +3,7 @@ export function parseReviewCommand(args) {
|
|
|
3
3
|
throw new Error('task ref and a supported review operation are required');
|
|
4
4
|
const values = {};
|
|
5
5
|
const flags = {
|
|
6
|
-
'--stage': 'stage', '--artifact': 'artifact', '--orchestrated': 'orchestrated', '--dry-run': 'dryRun'
|
|
7
|
-
'--override-ticket': 'overrideTicket', '--override-target': 'overrideTarget', '--override-scope': 'overrideScope'
|
|
6
|
+
'--stage': 'stage', '--artifact': 'artifact', '--orchestrated': 'orchestrated', '--dry-run': 'dryRun'
|
|
8
7
|
};
|
|
9
8
|
for (let index = 2; index < args.length; index += 1) {
|
|
10
9
|
const flag = args[index];
|
|
@@ -9,10 +9,8 @@ import { inspectDecisionDetailDuplicates } from './decision-details.js';
|
|
|
9
9
|
import { resolveTaskRef } from './resolve-ref.js';
|
|
10
10
|
import { validateLifecycleExecution } from './lifecycle-execution.js';
|
|
11
11
|
import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-lock.js';
|
|
12
|
-
import { allowsManualOverride } from './guard-override.js';
|
|
13
12
|
import { getArtifactSchema } from './artifact-schema.js';
|
|
14
13
|
import { canonicalSemanticDigest, inspectArtifactContract, sha256Content } from './artifact-operations.js';
|
|
15
|
-
import { expectedQualificationRelations, validateQualificationAudit } from './qualification-audit.js';
|
|
16
14
|
const STAGES = {
|
|
17
15
|
analysis: { family: 'review-analysis' },
|
|
18
16
|
plan: { family: 'review-plan' },
|
|
@@ -66,10 +64,8 @@ function preflightReviewSummaryUnlocked(request, options = {}) {
|
|
|
66
64
|
const publicationError = validateArtifactPublication(resolved.taskDir, spec.family, request.artifact);
|
|
67
65
|
if (publicationError)
|
|
68
66
|
return preflightFailed(request, 'REVIEW_ARTIFACT_IDENTITY_INVALID', publicationError.message, resolved.taskId);
|
|
69
|
-
let taskContent;
|
|
70
67
|
let content;
|
|
71
68
|
try {
|
|
72
|
-
taskContent = fs.readFileSync(resolved.taskMdPath, 'utf8');
|
|
73
69
|
const validated = validateCompletedArtifact(resolved.taskDir, spec.family, request.artifact);
|
|
74
70
|
if (!validated.ok)
|
|
75
71
|
return preflightFailed(request, validated.error.code === 'ARTIFACT_NOT_REGULAR' ? 'REVIEW_ARTIFACT_NOT_REGULAR' : 'REVIEW_ARTIFACT_IDENTITY_INVALID', validated.error.message, resolved.taskId);
|
|
@@ -89,22 +85,12 @@ function preflightReviewSummaryUnlocked(request, options = {}) {
|
|
|
89
85
|
const semanticDigest = canonicalSemanticDigest(content);
|
|
90
86
|
const detail = inspectDecisionDetailDuplicates(content);
|
|
91
87
|
const structure = inspectArtifactContract(content, getArtifactSchema(spec.family));
|
|
92
|
-
|
|
93
|
-
if (!expected.ok)
|
|
94
|
-
return preflightFailed(request, 'REVIEW_ARTIFACT_QUALIFICATION_INVALID', `${expected.code}: ${expected.message}`, resolved.taskId, { artifactSha256, semanticDigest });
|
|
95
|
-
const qualification = validateQualificationAudit(taskContent, content, {
|
|
96
|
-
family: spec.family,
|
|
97
|
-
artifact: request.artifact,
|
|
98
|
-
expectedUpstreamRelations: expected.relations
|
|
99
|
-
});
|
|
100
|
-
const qualificationError = qualification.ok ? null : qualification;
|
|
101
|
-
if (!detail.ok || !structure.ok || qualificationError) {
|
|
88
|
+
if (!detail.ok || !structure.ok) {
|
|
102
89
|
const diagnostics = [
|
|
103
90
|
...(!detail.ok ? [`${detail.code}: ${detail.message}`] : []),
|
|
104
|
-
...structure.diagnostics.map((item) => `${item.code}: ${item.message}`)
|
|
105
|
-
...(qualificationError ? [`${qualificationError.code}: ${qualificationError.message}`] : [])
|
|
91
|
+
...structure.diagnostics.map((item) => `${item.code}: ${item.message}`)
|
|
106
92
|
].join('; ');
|
|
107
|
-
return preflightFailed(request, !detail.ok ? 'REVIEW_DECISION_DETAIL_INVALID' :
|
|
93
|
+
return preflightFailed(request, !detail.ok ? 'REVIEW_DECISION_DETAIL_INVALID' : 'REVIEW_ARTIFACT_STRUCTURE_INVALID', diagnostics, resolved.taskId, {
|
|
108
94
|
artifactSha256, semanticDigest,
|
|
109
95
|
});
|
|
110
96
|
}
|
|
@@ -151,7 +137,7 @@ function prepareReviewSummaryCandidate(request, artifactContent, options = {}) {
|
|
|
151
137
|
taskId = resolved.taskId ?? null;
|
|
152
138
|
if (!resolved.ok)
|
|
153
139
|
return reject(resolved.code, resolved.message);
|
|
154
|
-
if (resolved.state !== 'active'
|
|
140
|
+
if (resolved.state !== 'active') {
|
|
155
141
|
return reject('TASK_STATE_MISMATCH', `task ${taskId} is ${resolved.state}, expected active`);
|
|
156
142
|
}
|
|
157
143
|
const parsed = parseArtifactName(request.artifact);
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
const REWORK_INTENT_HEADINGS = ['返工意图', 'Rework Intent'];
|
|
2
2
|
const REWORK_INTENT_COLUMNS = [
|
|
3
|
+
'intent_id', 'finding_id', 'source_artifact', 'source_sha256', 'target',
|
|
4
|
+
'classification', 'evidence_digest', 'task_fact_digest', 'status', 'declared_at', 'consumed_at'
|
|
5
|
+
];
|
|
6
|
+
const LEGACY_REWORK_INTENT_COLUMNS = [
|
|
3
7
|
'intent_id', 'finding_id', 'source_artifact', 'source_sha256', 'target',
|
|
4
8
|
'status', 'declared_at', 'consumed_at'
|
|
5
9
|
];
|
|
@@ -55,12 +59,16 @@ function parseReworkIntentDocument(content) {
|
|
|
55
59
|
throw new Error(`invalid intent id '${row.intent_id}'`);
|
|
56
60
|
if (!/^(AN|PL|CD)-[1-9]\d*$/.test(row.finding_id))
|
|
57
61
|
throw new Error(`invalid finding id '${row.finding_id}'`);
|
|
58
|
-
if (!['analysis', 'plan', 'code'].includes(row.target))
|
|
62
|
+
if (!['analysis', 'plan', 'code', 'pause'].includes(row.target))
|
|
59
63
|
throw new Error(`invalid rework target '${row.target}'`);
|
|
64
|
+
if (!['scope-or-requirement', 'design', 'implementation', 'human-decision', 'insufficient-evidence'].includes(row.classification))
|
|
65
|
+
throw new Error(`invalid rework classification '${row.classification}'`);
|
|
60
66
|
if (!['pending', 'consumed', 'superseded'].includes(row.status))
|
|
61
67
|
throw new Error(`invalid rework status '${row.status}'`);
|
|
62
68
|
if (!/^[a-f0-9]{64}$/.test(row.source_sha256))
|
|
63
69
|
throw new Error(`invalid source hash for '${row.intent_id}'`);
|
|
70
|
+
if (!/^[a-f0-9]{64}$/.test(row.evidence_digest) || !/^[a-f0-9]{64}$/.test(row.task_fact_digest))
|
|
71
|
+
throw new Error(`invalid semantic digest for '${row.intent_id}'`);
|
|
64
72
|
if (!row.source_artifact || !row.declared_at)
|
|
65
73
|
throw new Error(`rework intent '${row.intent_id}' has invalid required fields`);
|
|
66
74
|
const terminal = row.status === 'consumed' || row.status === 'superseded';
|
|
@@ -69,6 +77,8 @@ function parseReworkIntentDocument(content) {
|
|
|
69
77
|
return {
|
|
70
78
|
intentId: row.intent_id, findingId: row.finding_id, sourceArtifact: row.source_artifact,
|
|
71
79
|
sourceSha256: row.source_sha256, target: row.target,
|
|
80
|
+
classification: row.classification,
|
|
81
|
+
evidenceDigest: row.evidence_digest, taskFactDigest: row.task_fact_digest,
|
|
72
82
|
status: row.status, declaredAt: row.declared_at, consumedAt: row.consumed_at
|
|
73
83
|
};
|
|
74
84
|
});
|
|
@@ -91,7 +101,10 @@ function renderReworkIntents(intents) {
|
|
|
91
101
|
`| ${REWORK_INTENT_COLUMNS.map(() => '---').join(' | ')} |`,
|
|
92
102
|
...intents.map((intent) => `| ${[
|
|
93
103
|
intent.intentId, intent.findingId, intent.sourceArtifact, intent.sourceSha256,
|
|
94
|
-
intent.target, intent.
|
|
104
|
+
intent.target, intent.classification ?? (intent.target === 'analysis' ? 'scope-or-requirement' : intent.target === 'plan' ? 'design' : intent.target === 'code' ? 'implementation' : 'insufficient-evidence'),
|
|
105
|
+
intent.evidenceDigest ?? requireDigest(`${intent.findingId}|${intent.sourceArtifact}|${intent.sourceSha256}`),
|
|
106
|
+
intent.taskFactDigest ?? requireDigest(`current|${intent.findingId}|${intent.target}`),
|
|
107
|
+
intent.status, intent.declaredAt, intent.consumedAt
|
|
95
108
|
].map(escape).join(' | ')} |`)
|
|
96
109
|
].join('\n');
|
|
97
110
|
}
|
|
@@ -99,11 +112,63 @@ function upsertReworkIntent(intents, intent) {
|
|
|
99
112
|
const existing = intents.find((candidate) => candidate.intentId === intent.intentId);
|
|
100
113
|
if (!existing)
|
|
101
114
|
return { changed: true, intents: [...intents, intent] };
|
|
102
|
-
const identity = (value) => [value.intentId, value.findingId, value.sourceArtifact, value.sourceSha256, value.target];
|
|
115
|
+
const identity = (value) => [value.intentId, value.findingId, value.sourceArtifact, value.sourceSha256, value.target, value.classification, value.evidenceDigest, value.taskFactDigest];
|
|
103
116
|
if (JSON.stringify(identity(existing)) !== JSON.stringify(identity(intent)))
|
|
104
117
|
throw new Error(`intent '${intent.intentId}' identity conflicts with existing state`);
|
|
105
118
|
return { changed: false, intents };
|
|
106
119
|
}
|
|
120
|
+
function consumeHumanDecisionReworkIntents(intents, findingId, consumedAt) {
|
|
121
|
+
let changed = false;
|
|
122
|
+
const next = intents.map((intent) => {
|
|
123
|
+
if (intent.status !== 'pending' || intent.target !== 'pause' || intent.classification !== 'human-decision' || intent.findingId !== findingId)
|
|
124
|
+
return intent;
|
|
125
|
+
changed = true;
|
|
126
|
+
return { ...intent, status: 'consumed', consumedAt };
|
|
127
|
+
});
|
|
128
|
+
return { changed, intents: next };
|
|
129
|
+
}
|
|
130
|
+
function parseLegacyReworkIntentDocument(content) {
|
|
131
|
+
// TODO(compat): Remove this explicit decoder once no task contains the eight-column rework-intent header.
|
|
132
|
+
const body = sectionBody(content);
|
|
133
|
+
if (body === null)
|
|
134
|
+
return { ok: false, code: 'TASK_REWORK_INTENT_INVALID', message: 'legacy rework intent section is missing' };
|
|
135
|
+
const lines = body.split(/\r?\n/).filter((line) => line.trim());
|
|
136
|
+
const header = lines[0] ? cells(lines[0]) : null;
|
|
137
|
+
const separator = lines[1] ? cells(lines[1]) : null;
|
|
138
|
+
if (!header || !separator || header.length !== LEGACY_REWORK_INTENT_COLUMNS.length || !header.every((value, index) => value === LEGACY_REWORK_INTENT_COLUMNS[index])) {
|
|
139
|
+
return { ok: false, code: 'TASK_REWORK_INTENT_INVALID', message: 'rework intent section is not the known legacy table' };
|
|
140
|
+
}
|
|
141
|
+
try {
|
|
142
|
+
const intents = lines.slice(2).map((line) => {
|
|
143
|
+
const values = cells(line);
|
|
144
|
+
if (!values || values.length !== LEGACY_REWORK_INTENT_COLUMNS.length)
|
|
145
|
+
throw new Error('legacy rework intent row has the wrong column count');
|
|
146
|
+
const row = Object.fromEntries(LEGACY_REWORK_INTENT_COLUMNS.map((column, index) => [column, values[index]]));
|
|
147
|
+
if (!/^RI-[1-9]\d*$/.test(row.intent_id) || !/^(AN|PL|CD)-[1-9]\d*$/.test(row.finding_id))
|
|
148
|
+
throw new Error('legacy rework intent identity is invalid');
|
|
149
|
+
if (!['analysis', 'plan', 'code'].includes(row.target) || !['pending', 'consumed', 'superseded'].includes(row.status))
|
|
150
|
+
throw new Error('legacy rework intent state is invalid');
|
|
151
|
+
if (!/^[a-f0-9]{64}$/.test(row.source_sha256))
|
|
152
|
+
throw new Error('legacy rework intent source hash is invalid');
|
|
153
|
+
const legacyDigest = (value) => requireDigest(value);
|
|
154
|
+
const classification = row.target === 'analysis' ? 'scope-or-requirement' : row.target === 'plan' ? 'design' : 'implementation';
|
|
155
|
+
return {
|
|
156
|
+
intentId: row.intent_id, findingId: row.finding_id, sourceArtifact: row.source_artifact, sourceSha256: row.source_sha256,
|
|
157
|
+
target: row.target, classification,
|
|
158
|
+
evidenceDigest: legacyDigest(`${row.finding_id}|${row.source_artifact}|${row.source_sha256}`),
|
|
159
|
+
taskFactDigest: legacyDigest(`legacy|${row.finding_id}|${row.target}`),
|
|
160
|
+
status: row.status, declaredAt: row.declared_at, consumedAt: row.consumed_at
|
|
161
|
+
};
|
|
162
|
+
});
|
|
163
|
+
return { ok: true, present: true, intents };
|
|
164
|
+
}
|
|
165
|
+
catch (error) {
|
|
166
|
+
return { ok: false, code: 'TASK_REWORK_INTENT_INVALID', message: error instanceof Error ? error.message : String(error) };
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
function requireDigest(value) {
|
|
170
|
+
return createHash('sha256').update(value).digest('hex');
|
|
171
|
+
}
|
|
107
172
|
function consumeReworkIntents(intents, target, sourceHashes, consumedAt) {
|
|
108
173
|
let changed = false;
|
|
109
174
|
const next = intents.map((intent) => {
|
|
@@ -134,5 +199,6 @@ function reworkIntentMutation(content, intents) {
|
|
|
134
199
|
body: renderReworkIntents(intents)
|
|
135
200
|
};
|
|
136
201
|
}
|
|
137
|
-
export { REWORK_INTENT_COLUMNS, REWORK_INTENT_HEADINGS, parseReworkIntentDocument, renderReworkIntents, reworkIntentMutation, consumeReworkIntents, supersedeReworkIntents, upsertReworkIntent };
|
|
202
|
+
export { REWORK_INTENT_COLUMNS, REWORK_INTENT_HEADINGS, parseReworkIntentDocument, renderReworkIntents, reworkIntentMutation, consumeReworkIntents, supersedeReworkIntents, upsertReworkIntent, consumeHumanDecisionReworkIntents, parseLegacyReworkIntentDocument };
|
|
203
|
+
import { createHash } from 'node:crypto';
|
|
138
204
|
//# sourceMappingURL=rework-intent.js.map
|
|
@@ -56,25 +56,25 @@ function collectTaskSnapshot(taskRef, options = {}) {
|
|
|
56
56
|
const observations = [];
|
|
57
57
|
try {
|
|
58
58
|
const output = stripOneTrailingNewline((options.gitStatus ?? defaultGitStatus)(resolved.repoRoot));
|
|
59
|
-
observations.push({ name: 'git', status: 'ready', command: '
|
|
59
|
+
observations.push({ name: 'git', status: 'ready', command: 'git status --short --untracked-files=all', output, error: null });
|
|
60
60
|
}
|
|
61
61
|
catch (error) {
|
|
62
|
-
return failedSnapshot(resolved, taskRef, observations, 'git', '
|
|
62
|
+
return failedSnapshot(resolved, taskRef, observations, 'git', 'git status --short --untracked-files=all', 'SNAPSHOT_GIT_FAILED', error);
|
|
63
63
|
}
|
|
64
64
|
try {
|
|
65
65
|
const entries = (options.readDirectory ?? ((dir) => fs.readdirSync(dir, { withFileTypes: true })))(resolved.taskDir);
|
|
66
66
|
const output = renderDirectory(entries, resolved.taskDir, options.lstat ?? fs.lstatSync);
|
|
67
|
-
observations.push({ name: 'task-directory', status: 'ready', command:
|
|
67
|
+
observations.push({ name: 'task-directory', status: 'ready', command: `fs.readdirSync ${taskDirPath}/`, output, error: null });
|
|
68
68
|
}
|
|
69
69
|
catch (error) {
|
|
70
|
-
return failedSnapshot(resolved, taskRef, observations, 'task-directory',
|
|
70
|
+
return failedSnapshot(resolved, taskRef, observations, 'task-directory', `fs.readdirSync ${taskDirPath}/`, 'SNAPSHOT_DIRECTORY_READ_FAILED', error);
|
|
71
71
|
}
|
|
72
72
|
try {
|
|
73
73
|
const content = (options.readTask ?? ((file) => fs.readFileSync(file, 'utf8')))(resolved.taskMdPath);
|
|
74
|
-
observations.push({ name: 'task-tail', status: 'ready', command:
|
|
74
|
+
observations.push({ name: 'task-tail', status: 'ready', command: `fs.readFileSync ${taskPath} (last 10 lines)`, output: renderTail(content), error: null });
|
|
75
75
|
}
|
|
76
76
|
catch (error) {
|
|
77
|
-
return failedSnapshot(resolved, taskRef, observations, 'task-tail',
|
|
77
|
+
return failedSnapshot(resolved, taskRef, observations, 'task-tail', `fs.readFileSync ${taskPath} (last 10 lines)`, 'SNAPSHOT_TASK_READ_FAILED', error);
|
|
78
78
|
}
|
|
79
79
|
return {
|
|
80
80
|
status: 'ready', changed: false, requestRef: taskRef, taskId: resolved.taskId,
|