agentxchain 2.138.0 → 2.138.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentxchain",
3
- "version": "2.138.0",
3
+ "version": "2.138.1",
4
4
  "description": "CLI for AgentXchain — governed multi-agent software delivery",
5
5
  "type": "module",
6
6
  "bin": {
@@ -24,6 +24,14 @@ export async function intakeApproveCommand(opts) {
24
24
  console.log(JSON.stringify(result, null, 2));
25
25
  } else if (result.ok) {
26
26
  console.log('');
27
+ if (result.superseded) {
28
+ console.log(chalk.yellow(` Superseded intent ${result.intent.intent_id}`));
29
+ console.log(chalk.dim(` Approver: ${result.intent.approved_by}`));
30
+ console.log(chalk.dim(` Status: ${result.intent.history.at(-2)?.to || 'triaged'} → superseded`));
31
+ console.log(chalk.dim(` Reason: ${result.intent.archived_reason}`));
32
+ console.log('');
33
+ process.exit(result.exitCode);
34
+ }
27
35
  console.log(chalk.green(` Approved intent ${result.intent.intent_id}`));
28
36
  console.log(chalk.dim(` Approver: ${result.intent.approved_by}`));
29
37
  console.log(chalk.dim(` Status: triaged → approved`));
package/src/lib/intake.js CHANGED
@@ -21,6 +21,7 @@ import {
21
21
  archiveStaleIntentsForRun,
22
22
  migratePreBug34Intents,
23
23
  formatLegacyIntentMigrationNotice,
24
+ isPhantomIntent,
24
25
  } from './intent-startup-migration.js';
25
26
 
26
27
  const VALID_SOURCES = ['manual', 'ci_failure', 'git_ref_change', 'schedule', 'vision_scan'];
@@ -807,6 +808,16 @@ export function approveIntent(root, intentId, options = {}) {
807
808
  intent.status = 'approved';
808
809
  intent.approved_by = approver;
809
810
  intent.updated_at = now;
811
+
812
+ const phantomReason = 'planning artifacts for this intent already exist on disk; intent superseded during approval';
813
+ if (intent.approved_run_id && isPhantomIntent(root, intent)) {
814
+ intent.status = 'superseded';
815
+ intent.archived_reason = phantomReason;
816
+ intent.history.push({ from: previousStatus, to: 'superseded', at: now, reason: phantomReason, approver });
817
+ safeWriteJson(intentPath, intent);
818
+ return { ok: true, intent, superseded: true, exitCode: 0 };
819
+ }
820
+
810
821
  intent.history.push({ from: previousStatus, to: 'approved', at: now, reason, approver });
811
822
 
812
823
  safeWriteJson(intentPath, intent);
@@ -1,6 +1,7 @@
1
1
  import { existsSync, readFileSync, readdirSync } from 'node:fs';
2
2
  import { join } from 'node:path';
3
3
 
4
+ import { queryAcceptedTurnHistory } from './accepted-turn-history.js';
4
5
  import { safeWriteJson } from './safe-write.js';
5
6
  import { VALID_GOVERNED_TEMPLATE_IDS, loadGovernedTemplate } from './governed-templates.js';
6
7
 
@@ -28,7 +29,37 @@ function normalizeArtifactPaths(paths) {
28
29
  )];
29
30
  }
30
31
 
31
- function readPlanningGateFiles(root) {
32
+ function parseTimestamp(value) {
33
+ if (typeof value !== 'string' || !value.trim()) return null;
34
+ const parsed = Date.parse(value);
35
+ return Number.isFinite(parsed) ? parsed : null;
36
+ }
37
+
38
+ function hasPlanningHistoryEvidence(root, intent) {
39
+ const intentId = intent?.intent_id || null;
40
+ const runId = intent?.approved_run_id || null;
41
+ const intentTimestamp = parseTimestamp(intent?.approved_at)
42
+ ?? parseTimestamp(intent?.created_at)
43
+ ?? parseTimestamp(intent?.updated_at);
44
+
45
+ for (const entry of queryAcceptedTurnHistory(root)) {
46
+ if (entry?.phase !== 'planning') continue;
47
+
48
+ if (intentId && entry.intent_id === intentId) {
49
+ return true;
50
+ }
51
+
52
+ if (!runId || entry.run_id !== runId || intentTimestamp === null) continue;
53
+ const acceptedAt = parseTimestamp(entry.accepted_at);
54
+ if (acceptedAt !== null && acceptedAt >= intentTimestamp) {
55
+ return true;
56
+ }
57
+ }
58
+
59
+ return false;
60
+ }
61
+
62
+ function readPlanningGateFiles(root, intent) {
32
63
  const configPath = join(root, 'agentxchain.json');
33
64
  if (!existsSync(configPath)) return [];
34
65
 
@@ -39,27 +70,10 @@ function readPlanningGateFiles(root) {
39
70
  return [];
40
71
  }
41
72
 
42
- // Only use planning gate requires_files for phantom detection when:
43
- // 1. The planning gate has NOT been passed yet (once passed, these files
44
- // are expected to exist from normal planning work), AND
45
- // 2. At least one turn has been completed (turn_sequence > 0). If no turns
46
- // have been completed, the files are scaffolding templates, not evidence
47
- // of completed planning work. Without this check, ANY approved intent
48
- // in a freshly scaffolded project would be falsely detected as phantom.
49
- const statePath = join(root, '.agentxchain', 'state.json');
50
- try {
51
- const state = JSON.parse(readFileSync(statePath, 'utf8'));
52
- const gateStatus = state.phase_gate_status || {};
53
- const exitGateId = config?.routing?.planning?.exit_gate;
54
- if (exitGateId && gateStatus[exitGateId] === 'passed') return [];
55
- const turnSequence = state.turn_sequence || 0;
56
- if (turnSequence === 0) return [];
57
- } catch {
58
- // If state is unreadable, fall through to check gate files
59
- }
60
-
61
73
  const exitGateId = config?.routing?.planning?.exit_gate;
62
74
  const requiresFiles = exitGateId ? config?.gates?.[exitGateId]?.requires_files : null;
75
+ if (!Array.isArray(requiresFiles) || requiresFiles.length === 0) return [];
76
+ if (!hasPlanningHistoryEvidence(root, intent)) return [];
63
77
  return normalizeArtifactPaths(requiresFiles);
64
78
  }
65
79
 
@@ -146,7 +160,7 @@ export function listExpectedPlanningArtifacts(root, intent) {
146
160
  return normalizeArtifactPaths([
147
161
  ...recordedArtifacts,
148
162
  ...templateArtifacts,
149
- ...readPlanningGateFiles(root),
163
+ ...readPlanningGateFiles(root, intent),
150
164
  ]);
151
165
  }
152
166