@nimiplatform/nimi-coding 0.1.0 → 0.2.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.
Files changed (126) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/CODE_OF_CONDUCT.md +28 -0
  3. package/CONTRIBUTING.md +45 -0
  4. package/README.md +371 -344
  5. package/README.zh-CN.md +307 -0
  6. package/SECURITY.md +26 -0
  7. package/adapters/oh-my-codex/README.md +8 -9
  8. package/cli/commands/audit-sweep.mjs +10 -10
  9. package/cli/commands/classify-spec-tree.mjs +5 -0
  10. package/cli/commands/closeout.mjs +3 -0
  11. package/cli/commands/generate-spec-derived-docs.mjs +20 -0
  12. package/cli/commands/generate-spec-migration-plan.mjs +30 -0
  13. package/cli/commands/start.mjs +5 -1
  14. package/cli/commands/surface-validator-command.mjs +49 -0
  15. package/cli/commands/sweep-design.mjs +295 -0
  16. package/cli/commands/sweep.mjs +22 -0
  17. package/cli/commands/sync.mjs +132 -0
  18. package/cli/commands/topic-formatters.mjs +8 -8
  19. package/cli/commands/validate-ai-governance.mjs +167 -46
  20. package/cli/commands/validate-domain-admission.mjs +5 -0
  21. package/cli/commands/validate-guidance-bodies.mjs +5 -0
  22. package/cli/commands/validate-placement.mjs +5 -0
  23. package/cli/commands/validate-projection-edges.mjs +5 -0
  24. package/cli/commands/validate-spec-audit.mjs +5 -1
  25. package/cli/commands/validate-table-family.mjs +5 -0
  26. package/cli/commands/validate-tracked-output-admission.mjs +5 -0
  27. package/cli/constants.mjs +5 -49
  28. package/cli/help.mjs +33 -11
  29. package/cli/index.mjs +20 -2
  30. package/cli/lib/audit-sweep-runtime/admissions.mjs +38 -29
  31. package/cli/lib/audit-sweep-runtime/audit-validity.mjs +8 -0
  32. package/cli/lib/audit-sweep-runtime/chunks.mjs +11 -11
  33. package/cli/lib/audit-sweep-runtime/closeout.mjs +8 -8
  34. package/cli/lib/audit-sweep-runtime/codex-auditor-evidence.mjs +3 -3
  35. package/cli/lib/audit-sweep-runtime/codex-auditor.mjs +10 -10
  36. package/cli/lib/audit-sweep-runtime/common.mjs +7 -7
  37. package/cli/lib/audit-sweep-runtime/format.mjs +3 -3
  38. package/cli/lib/audit-sweep-runtime/ingest.mjs +8 -8
  39. package/cli/lib/audit-sweep-runtime/inventory-spec-chunks.mjs +24 -27
  40. package/cli/lib/audit-sweep-runtime/inventory.mjs +58 -18
  41. package/cli/lib/audit-sweep-runtime/ledger.mjs +1 -1
  42. package/cli/lib/audit-sweep-runtime/p0p1-profile.mjs +2 -2
  43. package/cli/lib/audit-sweep-runtime/remediation.mjs +6 -6
  44. package/cli/lib/audit-sweep-runtime/rerun.mjs +6 -6
  45. package/cli/lib/audit-sweep-runtime/status.mjs +1 -1
  46. package/cli/lib/audit-sweep-runtime/validators.mjs +2 -2
  47. package/cli/lib/authority-convergence.mjs +397 -2
  48. package/cli/lib/blueprint-audit.mjs +5 -5
  49. package/cli/lib/closeout.mjs +126 -3
  50. package/cli/lib/contracts.mjs +21 -17
  51. package/cli/lib/handoff.mjs +29 -11
  52. package/cli/lib/high-risk-admission.mjs +60 -11
  53. package/cli/lib/high-risk-decision.mjs +31 -2
  54. package/cli/lib/high-risk-ingest.mjs +5 -1
  55. package/cli/lib/high-risk-review.mjs +5 -1
  56. package/cli/lib/internal/contracts-parse.mjs +195 -24
  57. package/cli/lib/internal/contracts-validators.mjs +3 -2
  58. package/cli/lib/internal/doctor-bootstrap-surface.mjs +82 -35
  59. package/cli/lib/internal/doctor-delegated-surface.mjs +1 -1
  60. package/cli/lib/internal/doctor-finalize.mjs +12 -8
  61. package/cli/lib/internal/doctor-inspectors.mjs +34 -1
  62. package/cli/lib/internal/governance/ai/ai-context-budget-core.mjs +74 -12
  63. package/cli/lib/internal/governance/ai/ai-structure-budget-core.mjs +24 -6
  64. package/cli/lib/internal/governance/ai/check-agents-freshness.mjs +18 -23
  65. package/cli/lib/internal/surface-taxonomy-validators.mjs +931 -0
  66. package/cli/lib/internal/validators-spec.mjs +229 -20
  67. package/cli/lib/sweep-design-runtime/common.mjs +246 -0
  68. package/cli/lib/sweep-design-runtime/engine.mjs +733 -0
  69. package/cli/lib/sweep-design-runtime/fix-topic.mjs +414 -0
  70. package/cli/lib/sweep-design-runtime/lifecycle.mjs +54 -0
  71. package/cli/lib/sweep-design-runtime/results.mjs +324 -0
  72. package/cli/lib/sweep-design.mjs +8 -0
  73. package/cli/lib/sync.mjs +143 -0
  74. package/cli/lib/topic-artifacts.mjs +186 -0
  75. package/cli/lib/topic-authority-coverage.mjs +73 -0
  76. package/cli/lib/topic-closeout.mjs +560 -0
  77. package/cli/lib/topic-common.mjs +404 -0
  78. package/cli/lib/topic-decisions.mjs +332 -0
  79. package/cli/lib/topic-draft-packets.mjs +126 -7
  80. package/cli/lib/topic-execution.mjs +515 -0
  81. package/cli/lib/topic-goal.mjs +112 -33
  82. package/cli/lib/topic-ledger.mjs +281 -0
  83. package/cli/lib/topic-lifecycle-artifacts.mjs +173 -0
  84. package/cli/lib/topic-root-validation.mjs +288 -0
  85. package/cli/lib/topic-runner-commands.mjs +174 -0
  86. package/cli/lib/topic-runner-deferral.mjs +532 -0
  87. package/cli/lib/topic-runner-stale-gates.mjs +114 -0
  88. package/cli/lib/topic-runner-validation.mjs +138 -0
  89. package/cli/lib/topic-runner.mjs +109 -154
  90. package/cli/lib/topic-scaffold.mjs +252 -0
  91. package/cli/lib/topic-waves.mjs +403 -0
  92. package/cli/lib/topic.mjs +81 -93
  93. package/cli/lib/value-helpers.mjs +6 -1
  94. package/cli/seeds/bootstrap.mjs +96 -20
  95. package/cli/seeds/seed-policy.yaml +67 -0
  96. package/config/bootstrap.yaml +1 -1
  97. package/config/skill-manifest.yaml +4 -2
  98. package/config/spec-generation-inputs.yaml +41 -19
  99. package/contracts/audit-remediation-map.schema.yaml +1 -0
  100. package/contracts/audit-sweep-result.yaml +4 -0
  101. package/contracts/domain-admission.schema.yaml +56 -0
  102. package/contracts/migration-inventory.schema.yaml +80 -0
  103. package/contracts/negative-fixtures.yaml +91 -0
  104. package/contracts/placement-contract.schema.yaml +163 -0
  105. package/contracts/projection-edge.schema.yaml +130 -0
  106. package/contracts/shared-enums.yaml +68 -0
  107. package/contracts/spec-generation-audit.schema.yaml +19 -4
  108. package/contracts/spec-generation-inputs.schema.yaml +130 -29
  109. package/contracts/spec-reconstruction-result.yaml +9 -5
  110. package/contracts/surface-taxonomy.schema.yaml +201 -0
  111. package/contracts/sweep-design-result.yaml +349 -0
  112. package/contracts/table-family.schema.yaml +121 -0
  113. package/contracts/topic-goal.schema.yaml +10 -1
  114. package/contracts/tracked-output-admission.schema.yaml +70 -0
  115. package/contracts/workflow-consumer.schema.yaml +112 -0
  116. package/methodology/audit-sweep-p0p1-recall.yaml +1 -1
  117. package/methodology/spec-reconstruction.yaml +53 -30
  118. package/package.json +19 -4
  119. package/spec/_meta/command-gating-matrix.yaml +33 -0
  120. package/spec/_meta/generate-drift-migration-checklist.yaml +44 -62
  121. package/spec/_meta/governance-routing-cutover-checklist.yaml +3 -3
  122. package/spec/_meta/phase2-impacted-surface-matrix.yaml +14 -14
  123. package/spec/_meta/spec-authority-cutover-readiness.yaml +3 -5
  124. package/spec/_meta/spec-tree-model.yaml +104 -36
  125. package/spec/bootstrap-state.yaml +36 -36
  126. package/spec/product-scope.yaml +13 -10
@@ -1,4 +1,5 @@
1
1
  import path from "node:path";
2
+ import { fileURLToPath } from "node:url";
2
3
 
3
4
  import {
4
5
  loadBlueprintReference as loadBlueprintReferenceInternal,
@@ -27,6 +28,9 @@ import {
27
28
  validateSpecReconstructionSummary as validateSpecReconstructionSummaryInternal,
28
29
  } from "./internal/contracts-validators.mjs";
29
30
 
31
+ const PACKAGE_REF_PREFIX = "package://@nimiplatform/nimi-coding/";
32
+ const PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..");
33
+
30
34
  export function findCommandGatingRule(commandGatingMatrix, command, skillId = null) {
31
35
  return matchCommandGatingRule(commandGatingMatrix, command, skillId);
32
36
  }
@@ -117,9 +121,9 @@ async function loadYamlWithFallback(projectRoot, primaryRef, fallbackRef) {
117
121
  };
118
122
  }
119
123
 
120
- const fallbackText = await readTextIfFile(path.join(projectRoot, fallbackRef));
124
+ const fallbackText = await readTextIfFile(path.join(PACKAGE_ROOT, fallbackRef));
121
125
  return {
122
- path: fallbackRef,
126
+ path: `${PACKAGE_REF_PREFIX}${fallbackRef}`,
123
127
  text: fallbackText,
124
128
  data: parseYamlText(fallbackText),
125
129
  };
@@ -143,21 +147,21 @@ export async function loadTopicRuntimeContracts(projectRoot) {
143
147
  validationPolicy,
144
148
  authorityConvergencePolicy,
145
149
  ] = await Promise.all([
146
- loadYamlWithFallback(projectRoot, ".nimi/contracts/topic.schema.yaml", "nimi-coding/contracts/topic.schema.yaml"),
147
- loadYamlWithFallback(projectRoot, ".nimi/contracts/wave.schema.yaml", "nimi-coding/contracts/wave.schema.yaml"),
148
- loadYamlWithFallback(projectRoot, ".nimi/contracts/packet.schema.yaml", "nimi-coding/contracts/packet.schema.yaml"),
149
- loadYamlWithFallback(projectRoot, ".nimi/contracts/result.schema.yaml", "nimi-coding/contracts/result.schema.yaml"),
150
- loadYamlWithFallback(projectRoot, ".nimi/contracts/closeout.schema.yaml", "nimi-coding/contracts/closeout.schema.yaml"),
151
- loadYamlWithFallback(projectRoot, ".nimi/contracts/remediation.schema.yaml", "nimi-coding/contracts/remediation.schema.yaml"),
152
- loadYamlWithFallback(projectRoot, ".nimi/contracts/decision-review.schema.yaml", "nimi-coding/contracts/decision-review.schema.yaml"),
153
- loadYamlWithFallback(projectRoot, ".nimi/contracts/pending-note.schema.yaml", "nimi-coding/contracts/pending-note.schema.yaml"),
154
- loadYamlWithFallback(projectRoot, ".nimi/contracts/topic-step-decision.schema.yaml", "nimi-coding/contracts/topic-step-decision.schema.yaml"),
155
- loadYamlWithFallback(projectRoot, ".nimi/contracts/topic-run-ledger.schema.yaml", "nimi-coding/contracts/topic-run-ledger.schema.yaml"),
156
- loadYamlWithFallback(projectRoot, ".nimi/contracts/forbidden-shortcuts.catalog.yaml", "nimi-coding/contracts/forbidden-shortcuts.catalog.yaml"),
157
- loadYamlWithFallback(projectRoot, ".nimi/methodology/topic-lifecycle-report.yaml", "nimi-coding/methodology/topic-lifecycle-report.yaml"),
158
- loadYamlWithFallback(projectRoot, ".nimi/methodology/four-closure-policy.yaml", "nimi-coding/methodology/four-closure-policy.yaml"),
159
- loadYamlWithFallback(projectRoot, ".nimi/methodology/topic-validation-policy.yaml", "nimi-coding/methodology/topic-validation-policy.yaml"),
160
- loadYamlWithFallback(projectRoot, ".nimi/methodology/authority-convergence-policy.yaml", "nimi-coding/methodology/authority-convergence-policy.yaml"),
150
+ loadYamlWithFallback(projectRoot, ".nimi/contracts/topic.schema.yaml", "contracts/topic.schema.yaml"),
151
+ loadYamlWithFallback(projectRoot, ".nimi/contracts/wave.schema.yaml", "contracts/wave.schema.yaml"),
152
+ loadYamlWithFallback(projectRoot, ".nimi/contracts/packet.schema.yaml", "contracts/packet.schema.yaml"),
153
+ loadYamlWithFallback(projectRoot, ".nimi/contracts/result.schema.yaml", "contracts/result.schema.yaml"),
154
+ loadYamlWithFallback(projectRoot, ".nimi/contracts/closeout.schema.yaml", "contracts/closeout.schema.yaml"),
155
+ loadYamlWithFallback(projectRoot, ".nimi/contracts/remediation.schema.yaml", "contracts/remediation.schema.yaml"),
156
+ loadYamlWithFallback(projectRoot, ".nimi/contracts/decision-review.schema.yaml", "contracts/decision-review.schema.yaml"),
157
+ loadYamlWithFallback(projectRoot, ".nimi/contracts/pending-note.schema.yaml", "contracts/pending-note.schema.yaml"),
158
+ loadYamlWithFallback(projectRoot, ".nimi/contracts/topic-step-decision.schema.yaml", "contracts/topic-step-decision.schema.yaml"),
159
+ loadYamlWithFallback(projectRoot, ".nimi/contracts/topic-run-ledger.schema.yaml", "contracts/topic-run-ledger.schema.yaml"),
160
+ loadYamlWithFallback(projectRoot, ".nimi/contracts/forbidden-shortcuts.catalog.yaml", "contracts/forbidden-shortcuts.catalog.yaml"),
161
+ loadYamlWithFallback(projectRoot, ".nimi/methodology/topic-lifecycle-report.yaml", "methodology/topic-lifecycle-report.yaml"),
162
+ loadYamlWithFallback(projectRoot, ".nimi/methodology/four-closure-policy.yaml", "methodology/four-closure-policy.yaml"),
163
+ loadYamlWithFallback(projectRoot, ".nimi/methodology/topic-validation-policy.yaml", "methodology/topic-validation-policy.yaml"),
164
+ loadYamlWithFallback(projectRoot, ".nimi/methodology/authority-convergence-policy.yaml", "methodology/authority-convergence-policy.yaml"),
161
165
  ]);
162
166
 
163
167
  return {
@@ -8,7 +8,6 @@ import {
8
8
  HOST_ADAPTER_CONFIG_REF,
9
9
  SKILL_RESULT_CONTRACT_REFS,
10
10
  SPEC_GENERATION_AUDIT_CONTRACT_REF,
11
- SPEC_GENERATION_AUDIT_REF,
12
11
  SPEC_RECONSTRUCTION_RESULT_CONTRACT_REF,
13
12
  } from "../constants.mjs";
14
13
  import {
@@ -79,14 +78,34 @@ function evaluateSkillReadiness(skillId, doctorResult) {
79
78
  reason: "Bootstrap or handoff validation is failing; repair doctor errors before exporting handoff payloads",
80
79
  };
81
80
  }
82
- if (skillId === "spec_reconstruction") {
81
+ const usesV2SurfaceModel = doctorResult.specGenerationInputs?.mode === "class_filtered";
82
+ if (usesV2SurfaceModel && (doctorResult.commandGating?.entries ?? []).length === 0) {
83
+ if (skillId === "spec_reconstruction") {
84
+ return {
85
+ ok: true,
86
+ reason: "Projects may delegate spec reconstruction to an external AI host when canonical tree work is needed",
87
+ };
88
+ }
89
+ if (doctorResult.canonicalTree?.requiredFilesValid === true && doctorResult.specGenerationAudit?.ok === true) {
90
+ return {
91
+ ok: true,
92
+ reason: "Skill prerequisites are satisfied by the current project-local truth",
93
+ };
94
+ }
83
95
  return {
84
- ok: true,
85
- reason: "Projects may delegate spec reconstruction to an external AI host when canonical tree work is needed",
96
+ ok: false,
97
+ reason: "This skill is not allowed in the current lifecycle state",
86
98
  };
87
99
  }
88
100
  const rule = (doctorResult.commandGating?.entries ?? []).find((entry) => entry.command === "handoff" && entry.skill === skillId) ?? null;
89
- return commandRuleAllowsCurrentState(rule, doctorResult);
101
+ const gatedReadiness = commandRuleAllowsCurrentState(rule, doctorResult);
102
+ if (!gatedReadiness.ok || skillId !== "spec_reconstruction") {
103
+ return gatedReadiness;
104
+ }
105
+ return {
106
+ ok: true,
107
+ reason: "Projects may delegate spec reconstruction to an external AI host when canonical tree work is needed",
108
+ };
90
109
  }
91
110
  function getSkillSpecificExpectations(
92
111
  skillId,
@@ -108,8 +127,8 @@ function getSkillSpecificExpectations(
108
127
  expectedArtifactKinds: [],
109
128
  skillExpectedResults: [
110
129
  "generate_canonical_tree_under_.nimi/spec",
111
- "establish_kernel_markdown_and_kernel_tables_before_generated_views_or_guides",
112
- "record_file_level_generation_audit_under_.nimi/spec/_meta/spec-generation-audit.yaml",
130
+ "establish_kernel_markdown_and_kernel_tables_before_thin_guides",
131
+ "record_file_level_generation_audit_under_.nimi/local/state/spec-generation/spec-generation-audit.yaml",
113
132
  `satisfy_canonical_tree_completion_declared_in_${resultContractRef}`,
114
133
  ],
115
134
  };
@@ -264,7 +283,7 @@ export async function buildHandoffPayload(projectRoot, skillId) {
264
283
  acceptanceMode: specGenerationInputs.acceptanceMode ?? "canonical_tree_validity_without_blueprint",
265
284
  generationOrder: specGenerationInputs.generationOrder ?? [],
266
285
  inferenceRules: specGenerationInputs.inferenceRules ?? [],
267
- auditRef: SPEC_GENERATION_AUDIT_REF,
286
+ auditRef: ".nimi/local/state/spec-generation/spec-generation-audit.yaml",
268
287
  auditContractRef: SPEC_GENERATION_AUDIT_CONTRACT_REF,
269
288
  requiredFileClasses: [
270
289
  "INDEX.md",
@@ -272,7 +291,6 @@ export async function buildHandoffPayload(projectRoot, skillId) {
272
291
  "domain kernel/tables/**",
273
292
  ],
274
293
  optionalFileClasses: [
275
- "domain kernel/generated/**",
276
294
  "thin domain guides",
277
295
  ],
278
296
  minimalRequiredOutputs: Array.isArray(specReconstructionDocument?.reconstruction?.target_tree_shape?.minimal_required_outputs)
@@ -586,8 +604,8 @@ export function formatHandoffPrompt(payload) {
586
604
  "- 直接在 `.nimi/spec/**` 下构建 canonical tree;不要把 compact summary 当作主要输出。",
587
605
  ));
588
606
  lines.push(localize(
589
- "- Write `.nimi/spec/_meta/spec-generation-audit.yaml` alongside the canonical tree. Every generated canonical file must record source refs, source basis, and unresolved gaps.",
590
- "- canonical tree 旁边写出 `.nimi/spec/_meta/spec-generation-audit.yaml`。每个生成的 canonical 文件都必须记录 source refs、source basis 和未解决缺口。",
607
+ "- Write `.nimi/local/state/spec-generation/spec-generation-audit.yaml` as local generation state. Every canonical file must record source refs, source basis, and unresolved gaps.",
608
+ "- `.nimi/local/state/spec-generation/spec-generation-audit.yaml` 写作本地生成状态。每个 canonical 文件都必须记录 source refs、source basis 和未解决缺口。",
591
609
  ));
592
610
  lines.push(localize(
593
611
  "- When a benchmark blueprint root is declared, aim for semantic and structural parity with that benchmark rather than byte-for-byte duplication.",
@@ -26,6 +26,34 @@ import { parseYamlText } from "./yaml-helpers.mjs";
26
26
 
27
27
  const ADMISSIONS_SPEC_REF = ".nimi/spec/high-risk-admissions.yaml";
28
28
 
29
+ async function resolveProjectContainedPath(projectRoot, inputPath, artifactLabel) {
30
+ const absolutePath = path.resolve(projectRoot, inputPath);
31
+ let resolvedProjectRoot;
32
+ let resolvedPath;
33
+ try {
34
+ resolvedProjectRoot = await realpath(projectRoot);
35
+ resolvedPath = await realpath(absolutePath);
36
+ } catch {
37
+ return {
38
+ ok: false,
39
+ path: absolutePath,
40
+ reason: `cannot read ${artifactLabel} at ${absolutePath}`,
41
+ };
42
+ }
43
+ const relative = path.relative(resolvedProjectRoot, resolvedPath);
44
+ if (relative.startsWith("..") || path.isAbsolute(relative)) {
45
+ return {
46
+ ok: false,
47
+ path: absolutePath,
48
+ reason: `${artifactLabel} must resolve inside the current project root`,
49
+ };
50
+ }
51
+ return {
52
+ ok: true,
53
+ path: resolvedPath,
54
+ };
55
+ }
56
+
29
57
  function translateAdmissionReason(reason) {
30
58
  const translations = new Map([
31
59
  ["imported decision payload must declare contractVersion nimicoding.high-risk-decision.v1", "导入的 decision payload 必须声明 contractVersion nimicoding.high-risk-decision.v1"],
@@ -45,15 +73,25 @@ function translateAdmissionReason(reason) {
45
73
  }
46
74
 
47
75
  async function loadImportedDecisionPayload(projectRoot, fromPath) {
48
- const absolutePath = path.resolve(projectRoot, fromPath);
49
- const rawText = await readTextIfFile(absolutePath);
76
+ const containedPath = await resolveProjectContainedPath(projectRoot, fromPath, "imported decision JSON");
77
+ if (!containedPath.ok) {
78
+ return {
79
+ ok: false,
80
+ error: `${localize(
81
+ `nimicoding admit-high-risk-decision refused: ${containedPath.reason}.`,
82
+ `nimicoding admit-high-risk-decision 已拒绝:${containedPath.reason}。`,
83
+ )}\n`,
84
+ };
85
+ }
86
+
87
+ const rawText = await readTextIfFile(containedPath.path);
50
88
 
51
89
  if (rawText === null) {
52
90
  return {
53
91
  ok: false,
54
92
  error: `${localize(
55
- `nimicoding admit-high-risk-decision refused: cannot read imported decision JSON at ${absolutePath}.`,
56
- `nimicoding admit-high-risk-decision 已拒绝:无法读取 ${absolutePath} 处的导入 decision JSON。`,
93
+ `nimicoding admit-high-risk-decision refused: cannot read imported decision JSON at ${containedPath.path}.`,
94
+ `nimicoding admit-high-risk-decision 已拒绝:无法读取 ${containedPath.path} 处的导入 decision JSON。`,
57
95
  )}\n`,
58
96
  };
59
97
  }
@@ -65,8 +103,8 @@ async function loadImportedDecisionPayload(projectRoot, fromPath) {
65
103
  return {
66
104
  ok: false,
67
105
  error: `${localize(
68
- `nimicoding admit-high-risk-decision refused: imported decision JSON at ${absolutePath} is invalid JSON.`,
69
- `nimicoding admit-high-risk-decision 已拒绝:${absolutePath} 处的导入 decision JSON 不是合法 JSON。`,
106
+ `nimicoding admit-high-risk-decision refused: imported decision JSON at ${containedPath.path} is invalid JSON.`,
107
+ `nimicoding admit-high-risk-decision 已拒绝:${containedPath.path} 处的导入 decision JSON 不是合法 JSON。`,
70
108
  )}\n`,
71
109
  };
72
110
  }
@@ -110,7 +148,7 @@ async function loadImportedDecisionPayload(projectRoot, fromPath) {
110
148
 
111
149
  return {
112
150
  ok: true,
113
- path: absolutePath,
151
+ path: containedPath.path,
114
152
  payload: parsed,
115
153
  };
116
154
  }
@@ -192,8 +230,15 @@ async function loadAdmissionsSpec(projectRoot, contract) {
192
230
  }
193
231
 
194
232
  async function loadPacketIdentity(projectRoot, packetRef) {
195
- const absolutePath = path.resolve(projectRoot, packetRef);
196
- const packetValidation = await validateExecutionPacket(absolutePath);
233
+ const containedPath = await resolveProjectContainedPath(projectRoot, packetRef, "attached packet_ref");
234
+ if (!containedPath.ok) {
235
+ return {
236
+ ok: false,
237
+ reason: containedPath.reason,
238
+ };
239
+ }
240
+
241
+ const packetValidation = await validateExecutionPacket(containedPath.path);
197
242
  if (!packetValidation.ok) {
198
243
  return {
199
244
  ok: false,
@@ -202,7 +247,7 @@ async function loadPacketIdentity(projectRoot, packetRef) {
202
247
  };
203
248
  }
204
249
 
205
- const text = await readTextIfFile(absolutePath);
250
+ const text = await readTextIfFile(containedPath.path);
206
251
  const parsed = parseYamlText(text);
207
252
  const packetId = String(parsed?.packet_id ?? "");
208
253
  const topicId = String(parsed?.topic_id ?? "");
@@ -242,7 +287,11 @@ function evaluateAdmissionReadiness(doctorResult, admissionsSpec, packetIdentity
242
287
  };
243
288
  }
244
289
 
245
- if (doctorResult.lifecycleState?.treeState !== "canonical_tree_ready" || doctorResult.canonicalTree?.requiredFilesValid !== true) {
290
+ const v2Ready = doctorResult.specGenerationInputs?.mode === "class_filtered"
291
+ && doctorResult.canonicalTree?.requiredFilesValid === true;
292
+ const legacyReady = doctorResult.lifecycleState?.treeState === "canonical_tree_ready"
293
+ && doctorResult.canonicalTree?.requiredFilesValid === true;
294
+ if (!v2Ready && !legacyReady) {
246
295
  return {
247
296
  ok: false,
248
297
  reason: "Canonical admission requires canonical_tree_ready with declared canonical files present",
@@ -150,8 +150,33 @@ function validateImportedReviewPayload(payload) {
150
150
  return { ok: true };
151
151
  }
152
152
 
153
+ function extractMarkdownSectionBody(text, heading) {
154
+ const lines = text.split(/\r?\n/);
155
+ let sectionLevel = null;
156
+ const body = [];
157
+ for (const line of lines) {
158
+ const headingMatch = line.match(/^(#{1,6})\s+(.+?)\s*#*\s*$/);
159
+ if (headingMatch) {
160
+ const level = headingMatch[1].length;
161
+ const title = headingMatch[2].trim();
162
+ if (sectionLevel !== null && level <= sectionLevel) {
163
+ break;
164
+ }
165
+ if (title === heading) {
166
+ sectionLevel = level;
167
+ continue;
168
+ }
169
+ }
170
+ if (sectionLevel !== null) {
171
+ body.push(line);
172
+ }
173
+ }
174
+ return sectionLevel === null ? "" : body.join("\n");
175
+ }
176
+
153
177
  function extractAcceptanceDisposition(text) {
154
- const match = text.match(/Disposition:\s*(\w+)/i);
178
+ const dispositionSection = extractMarkdownSectionBody(text, "Current Phase Disposition");
179
+ const match = dispositionSection.match(/Disposition:\s*(\w+)/i);
155
180
  return match ? match[1].toLowerCase() : null;
156
181
  }
157
182
 
@@ -163,7 +188,11 @@ function evaluateDecisionReadiness(doctorResult, acceptanceReport) {
163
188
  };
164
189
  }
165
190
 
166
- if (doctorResult.lifecycleState?.treeState !== "canonical_tree_ready" || doctorResult.canonicalTree?.requiredFilesValid !== true) {
191
+ const v2Ready = doctorResult.specGenerationInputs?.mode === "class_filtered"
192
+ && doctorResult.canonicalTree?.requiredFilesValid === true;
193
+ const legacyReady = doctorResult.lifecycleState?.treeState === "canonical_tree_ready"
194
+ && doctorResult.canonicalTree?.requiredFilesValid === true;
195
+ if (!v2Ready && !legacyReady) {
167
196
  return {
168
197
  ok: false,
169
198
  reason: "High-risk decision projection requires canonical_tree_ready with declared canonical files present",
@@ -149,7 +149,11 @@ function evaluateHighRiskIngestReadiness(doctorResult, validations) {
149
149
  };
150
150
  }
151
151
 
152
- if (doctorResult.lifecycleState?.treeState !== "canonical_tree_ready" || doctorResult.canonicalTree?.requiredFilesValid !== true) {
152
+ const v2Ready = doctorResult.specGenerationInputs?.mode === "class_filtered"
153
+ && doctorResult.canonicalTree?.requiredFilesValid === true;
154
+ const legacyReady = doctorResult.lifecycleState?.treeState === "canonical_tree_ready"
155
+ && doctorResult.canonicalTree?.requiredFilesValid === true;
156
+ if (!v2Ready && !legacyReady) {
153
157
  return {
154
158
  ok: false,
155
159
  reason: "High-risk ingest requires canonical_tree_ready with declared canonical files present",
@@ -152,7 +152,11 @@ function evaluateHighRiskReviewReadiness(doctorResult) {
152
152
  };
153
153
  }
154
154
 
155
- if (doctorResult.lifecycleState?.treeState !== "canonical_tree_ready" || doctorResult.canonicalTree?.requiredFilesValid !== true) {
155
+ const v2Ready = doctorResult.specGenerationInputs?.mode === "class_filtered"
156
+ && doctorResult.canonicalTree?.requiredFilesValid === true;
157
+ const legacyReady = doctorResult.lifecycleState?.treeState === "canonical_tree_ready"
158
+ && doctorResult.canonicalTree?.requiredFilesValid === true;
159
+ if (!v2Ready && !legacyReady) {
156
160
  return {
157
161
  ok: false,
158
162
  reason: "High-risk review projection requires canonical_tree_ready with declared canonical files present",