agentxchain 2.155.71 → 2.155.73

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.
@@ -138,7 +138,7 @@ export function validateStagedTurnResult(root, state, config, opts = {}) {
138
138
  }
139
139
 
140
140
  // ── Stage C: Artifact Validation ───────────────────────────────────────
141
- const artifactResult = validateArtifact(turnResult, config);
141
+ const artifactResult = validateArtifact(turnResult, config, state);
142
142
  if (artifactResult.errors.length > 0) {
143
143
  return result('artifact', 'artifact_error', artifactResult.errors, artifactResult.warnings);
144
144
  }
@@ -670,7 +670,7 @@ function validateAssignment(tr, state) {
670
670
 
671
671
  // ── Stage C: Artifact Validation ─────────────────────────────────────────────
672
672
 
673
- function validateArtifact(tr, config) {
673
+ function validateArtifact(tr, config, state = null) {
674
674
  const errors = [];
675
675
  const warnings = [];
676
676
 
@@ -699,6 +699,11 @@ function validateArtifact(tr, config) {
699
699
  `Artifact type "workspace" requires authoritative write authority, but role "${tr.role}" has "${writeAuthority}".`
700
700
  );
701
701
  }
702
+ if ((tr.files_changed || []).length === 0 && !hasCheckpointableProducedFiles(tr)) {
703
+ errors.push(
704
+ 'artifact.type: "workspace" but files_changed is empty. Use artifact type "review" for no-edit turns, or declare checkpointable verification.produced_files entries with disposition "artifact".'
705
+ );
706
+ }
702
707
  }
703
708
 
704
709
  // Check for reserved path modifications
@@ -722,9 +727,17 @@ function validateArtifact(tr, config) {
722
727
  }
723
728
  }
724
729
 
725
- // Warn if files_changed is empty for authoritative + completed turns
726
- if (writeAuthority === 'authoritative' && tr.status === 'completed' && (tr.files_changed || []).length === 0) {
727
- warnings.push('Authoritative role completed with no files_changed — is this intentional?');
730
+ // Implementation-phase completion must be backed by actual product code
731
+ // changes. Planning/review artifacts are supplementary and should not satisfy
732
+ // the implementation_complete gate by themselves.
733
+ if (writeAuthority === 'authoritative' && state?.phase === 'implementation' && tr.status === 'completed') {
734
+ const productFiles = (tr.files_changed || []).filter(f => isProductChangePath(f));
735
+ if (productFiles.length === 0) {
736
+ errors.push(
737
+ `Role "${tr.role}" completed an implementation turn without product code changes in files_changed. ` +
738
+ 'Implementation-phase completion requires at least one non-planning, non-review repo path; planning artifacts alone are not sufficient.'
739
+ );
740
+ }
728
741
  }
729
742
 
730
743
  // Validate proposed_changes for proposed runtimes that cannot write repo files directly.
@@ -772,6 +785,24 @@ function isAllowedReviewPath(filePath) {
772
785
  return filePath.startsWith('.planning/') || filePath.startsWith('.agentxchain/reviews/');
773
786
  }
774
787
 
788
+ function isProductChangePath(filePath) {
789
+ return typeof filePath === 'string'
790
+ && filePath.trim().length > 0
791
+ && !isAllowedReviewPath(filePath)
792
+ && !filePath.startsWith('.agentxchain/staging/');
793
+ }
794
+
795
+ function hasCheckpointableProducedFiles(tr) {
796
+ return Array.isArray(tr.verification?.produced_files)
797
+ && tr.verification.produced_files.some((entry) => (
798
+ entry
799
+ && typeof entry === 'object'
800
+ && typeof entry.path === 'string'
801
+ && entry.path.trim()
802
+ && (entry.disposition == null || entry.disposition === 'artifact')
803
+ ));
804
+ }
805
+
775
806
  // ── Stage D: Verification Validation ─────────────────────────────────────────
776
807
 
777
808
  function validateVerification(tr) {
@@ -1488,7 +1519,12 @@ export function normalizeTurnResult(tr, config, context = {}) {
1488
1519
  && !Array.isArray(normalized.artifact)
1489
1520
  && normalized.artifact.type === 'workspace'
1490
1521
  && filesChangedIsEmpty
1491
- && (context.forceReviewArtifact || hasExplicitNoEditLifecycleSignal)
1522
+ && !hasCheckpointableProducedFiles(normalized)
1523
+ && (
1524
+ context.forceReviewArtifact
1525
+ || hasExplicitNoEditLifecycleSignal
1526
+ || (normalized.status === 'needs_human' && normalized.proposed_next_role === 'human')
1527
+ )
1492
1528
  ) {
1493
1529
  normalized.artifact = {
1494
1530
  ...normalized.artifact,
@@ -15,6 +15,13 @@ import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
15
15
  import { join, resolve as pathResolve, isAbsolute } from 'node:path';
16
16
  import { createHash } from 'node:crypto';
17
17
 
18
+ const ROADMAP_TRACKING_ANNOTATION_PATTERN = /<!--\s*tracking\s*:[\s\S]*?-->/i;
19
+
20
+ export function stripRoadmapTrackingAnnotations(text) {
21
+ if (typeof text !== 'string') return '';
22
+ return text.replace(ROADMAP_TRACKING_ANNOTATION_PATTERN, '').replace(/\s+/g, ' ').trim();
23
+ }
24
+
18
25
  // ---------------------------------------------------------------------------
19
26
  // Parsing
20
27
  // ---------------------------------------------------------------------------
@@ -259,8 +266,10 @@ export function deriveRoadmapCandidates(root, roadmapPath = '.planning/ROADMAP.m
259
266
 
260
267
  const uncheckedMatch = line.match(/^\s*[-*]\s+\[\s\]\s+(.+?)\s*$/);
261
268
  if (!uncheckedMatch || !currentMilestone) continue;
269
+ if (ROADMAP_TRACKING_ANNOTATION_PATTERN.test(line)) continue;
262
270
 
263
- const goal = uncheckedMatch[1].trim();
271
+ const goal = stripRoadmapTrackingAnnotations(uncheckedMatch[1]);
272
+ if (!goal) continue;
264
273
  const combinedGoal = `${currentMilestone}: ${goal}`;
265
274
  if (isGoalAddressed(combinedGoal, allSignals) || isGoalAddressed(goal, allSignals)) {
266
275
  continue;
@@ -484,6 +493,7 @@ export function detectRoadmapExhaustedVisionOpen(root, visionPath, roadmapPath =
484
493
  continue;
485
494
  }
486
495
  if (currentMilestone && /^\s*[-*]\s+\[\s\]/.test(line)) {
496
+ if (ROADMAP_TRACKING_ANNOTATION_PATTERN.test(line)) continue;
487
497
  hasUnchecked = true;
488
498
  }
489
499
  }