@nimiplatform/nimi-coding 0.2.8 → 0.3.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/CHANGELOG.md +10 -90
- package/CONTRIBUTING.md +5 -34
- package/README.md +47 -350
- package/README.zh-CN.md +33 -287
- package/cli/commands/start.mjs +48 -688
- package/cli/commands/validate-spec-audit.mjs +1 -5
- package/cli/commands/validate-spec-governance.mjs +20 -0
- package/cli/constants.mjs +9 -417
- package/cli/help.mjs +28 -145
- package/cli/index.mjs +5 -33
- package/cli/lib/blueprint-audit.mjs +5 -12
- package/cli/lib/bootstrap.mjs +0 -9
- package/cli/lib/contracts.mjs +0 -163
- package/cli/lib/doctor.mjs +155 -10
- package/cli/lib/entrypoints.mjs +63 -167
- package/cli/lib/internal/contracts-loaders.mjs +0 -85
- package/cli/lib/internal/contracts-parse.mjs +84 -520
- package/cli/lib/internal/governance/ai/check-agents-freshness.mjs +13 -8
- package/cli/lib/internal/surface-taxonomy-validators.mjs +316 -66
- package/cli/lib/internal/validators-shared.mjs +0 -16
- package/cli/lib/internal/validators-spec-helpers.mjs +7 -140
- package/cli/lib/internal/validators-spec.mjs +103 -342
- package/cli/lib/shared.mjs +1 -42
- package/cli/lib/validators.mjs +0 -48
- package/cli/seeds/seed-policy.yaml +0 -3
- package/config/bootstrap.yaml +4 -4
- package/config/spec-generation-inputs.yaml +6 -12
- package/contracts/domain-admission.schema.yaml +3 -3
- package/contracts/migration-inventory.schema.yaml +12 -75
- package/contracts/negative-fixtures.yaml +19 -99
- package/contracts/placement-contract.schema.yaml +49 -78
- package/contracts/projection-edge.schema.yaml +37 -121
- package/contracts/shared-enums.yaml +4 -8
- package/contracts/spec-generation-inputs.schema.yaml +15 -112
- package/contracts/spec-layout.schema.yaml +32 -0
- package/contracts/surface-taxonomy.schema.yaml +43 -94
- package/contracts/table-family.schema.yaml +1 -1
- package/contracts/tracked-output-admission.schema.yaml +13 -66
- package/methodology/core.yaml +33 -25
- package/methodology/four-closure-policy.yaml +18 -26
- package/methodology/role-separation-policy.yaml +21 -27
- package/methodology/spec-reconstruction.yaml +17 -34
- package/package.json +2 -4
- package/spec/_meta/spec-tree-model.yaml +5 -101
- package/spec/product-scope.yaml +25 -57
- package/adapters/README.md +0 -25
- package/adapters/claude/README.md +0 -89
- package/adapters/claude/profile.yaml +0 -70
- package/adapters/codex/README.md +0 -53
- package/adapters/codex/profile.yaml +0 -78
- package/adapters/oh-my-codex/README.md +0 -184
- package/adapters/oh-my-codex/profile.yaml +0 -46
- package/cli/commands/admit-high-risk-decision.mjs +0 -108
- package/cli/commands/audit-sweep.mjs +0 -364
- package/cli/commands/closeout.mjs +0 -186
- package/cli/commands/decide-high-risk-execution.mjs +0 -124
- package/cli/commands/handoff.mjs +0 -123
- package/cli/commands/ingest-high-risk-execution.mjs +0 -95
- package/cli/commands/review-high-risk-execution.mjs +0 -95
- package/cli/commands/sweep-design.mjs +0 -295
- package/cli/commands/sweep.mjs +0 -22
- package/cli/commands/topic-formatters.mjs +0 -382
- package/cli/commands/topic-goal.mjs +0 -33
- package/cli/commands/topic-options-shared.mjs +0 -27
- package/cli/commands/topic-options-workflow.mjs +0 -767
- package/cli/commands/topic-options.mjs +0 -626
- package/cli/commands/topic-runner.mjs +0 -169
- package/cli/commands/topic.mjs +0 -795
- package/cli/commands/validate-acceptance.mjs +0 -5
- package/cli/commands/validate-execution-packet.mjs +0 -5
- package/cli/commands/validate-orchestration-state.mjs +0 -5
- package/cli/commands/validate-prompt.mjs +0 -5
- package/cli/commands/validate-worker-output.mjs +0 -5
- package/cli/lib/adapter-profiles.mjs +0 -403
- package/cli/lib/audit-execution.mjs +0 -52
- package/cli/lib/audit-sweep-runtime/admissions.mjs +0 -508
- package/cli/lib/audit-sweep-runtime/audit-validity.mjs +0 -356
- package/cli/lib/audit-sweep-runtime/chunks.mjs +0 -697
- package/cli/lib/audit-sweep-runtime/claude-auditor.mjs +0 -658
- package/cli/lib/audit-sweep-runtime/closeout.mjs +0 -144
- package/cli/lib/audit-sweep-runtime/codex-auditor-evidence.mjs +0 -654
- package/cli/lib/audit-sweep-runtime/codex-auditor.mjs +0 -527
- package/cli/lib/audit-sweep-runtime/common.mjs +0 -341
- package/cli/lib/audit-sweep-runtime/coverage-quality.mjs +0 -172
- package/cli/lib/audit-sweep-runtime/evidence-assignment.mjs +0 -155
- package/cli/lib/audit-sweep-runtime/format.mjs +0 -57
- package/cli/lib/audit-sweep-runtime/ingest.mjs +0 -486
- package/cli/lib/audit-sweep-runtime/inventory-spec-chunks.mjs +0 -425
- package/cli/lib/audit-sweep-runtime/inventory.mjs +0 -798
- package/cli/lib/audit-sweep-runtime/ledger.mjs +0 -315
- package/cli/lib/audit-sweep-runtime/p0p1-profile.mjs +0 -101
- package/cli/lib/audit-sweep-runtime/remediation.mjs +0 -349
- package/cli/lib/audit-sweep-runtime/rerun.mjs +0 -129
- package/cli/lib/audit-sweep-runtime/risk-budget.mjs +0 -300
- package/cli/lib/audit-sweep-runtime/status.mjs +0 -62
- package/cli/lib/audit-sweep-runtime/validators-ledger.mjs +0 -215
- package/cli/lib/audit-sweep-runtime/validators.mjs +0 -797
- package/cli/lib/audit-sweep.mjs +0 -19
- package/cli/lib/authority-convergence.mjs +0 -720
- package/cli/lib/closeout.mjs +0 -732
- package/cli/lib/codex-sdk-runner.mjs +0 -76
- package/cli/lib/external-execution.mjs +0 -101
- package/cli/lib/handoff.mjs +0 -812
- package/cli/lib/high-risk-admission.mjs +0 -512
- package/cli/lib/high-risk-decision.mjs +0 -353
- package/cli/lib/high-risk-ingest.mjs +0 -321
- package/cli/lib/high-risk-review.mjs +0 -267
- package/cli/lib/internal/contracts-parse-high-risk.mjs +0 -131
- package/cli/lib/internal/contracts-validators.mjs +0 -399
- package/cli/lib/internal/doctor-bootstrap-surface.mjs +0 -406
- package/cli/lib/internal/doctor-delegated-surface.mjs +0 -256
- package/cli/lib/internal/doctor-finalize.mjs +0 -383
- package/cli/lib/internal/doctor-format.mjs +0 -286
- package/cli/lib/internal/doctor-inspectors.mjs +0 -327
- package/cli/lib/internal/doctor-state.mjs +0 -205
- package/cli/lib/internal/validators-artifacts.mjs +0 -515
- package/cli/lib/sweep-design-runtime/common.mjs +0 -246
- package/cli/lib/sweep-design-runtime/engine.mjs +0 -733
- package/cli/lib/sweep-design-runtime/fix-topic.mjs +0 -414
- package/cli/lib/sweep-design-runtime/lifecycle.mjs +0 -54
- package/cli/lib/sweep-design-runtime/results.mjs +0 -324
- package/cli/lib/sweep-design.mjs +0 -8
- package/cli/lib/topic-artifacts.mjs +0 -186
- package/cli/lib/topic-authority-coverage.mjs +0 -73
- package/cli/lib/topic-closeout.mjs +0 -560
- package/cli/lib/topic-common.mjs +0 -404
- package/cli/lib/topic-decisions.mjs +0 -332
- package/cli/lib/topic-draft-packets.mjs +0 -167
- package/cli/lib/topic-execution.mjs +0 -533
- package/cli/lib/topic-goal.mjs +0 -440
- package/cli/lib/topic-ledger.mjs +0 -281
- package/cli/lib/topic-lifecycle-artifacts.mjs +0 -173
- package/cli/lib/topic-root-validation.mjs +0 -288
- package/cli/lib/topic-runner-commands.mjs +0 -174
- package/cli/lib/topic-runner-deferral.mjs +0 -532
- package/cli/lib/topic-runner-stale-gates.mjs +0 -114
- package/cli/lib/topic-runner-validation.mjs +0 -138
- package/cli/lib/topic-runner.mjs +0 -727
- package/cli/lib/topic-scaffold.mjs +0 -252
- package/cli/lib/topic-waves.mjs +0 -403
- package/cli/lib/topic.mjs +0 -81
- package/config/audit-execution-artifacts.yaml +0 -20
- package/config/external-execution-artifacts.yaml +0 -16
- package/config/host-adapter.yaml +0 -30
- package/config/host-profile.yaml +0 -29
- package/config/installer-evidence.yaml +0 -31
- package/config/skill-installer.yaml +0 -23
- package/config/skill-manifest.yaml +0 -48
- package/config/skills.yaml +0 -30
- package/contracts/acceptance.schema.yaml +0 -16
- package/contracts/admission-checklist.schema.yaml +0 -15
- package/contracts/audit-chunk.schema.yaml +0 -123
- package/contracts/audit-closeout.schema.yaml +0 -51
- package/contracts/audit-finding.schema.yaml +0 -61
- package/contracts/audit-ledger.schema.yaml +0 -138
- package/contracts/audit-plan.schema.yaml +0 -138
- package/contracts/audit-remediation-map.schema.yaml +0 -52
- package/contracts/audit-rerun.schema.yaml +0 -31
- package/contracts/audit-sweep-result.yaml +0 -53
- package/contracts/authority-convergence-audit.schema.yaml +0 -19
- package/contracts/closeout.schema.yaml +0 -25
- package/contracts/decision-review.schema.yaml +0 -16
- package/contracts/doc-spec-audit-result.yaml +0 -19
- package/contracts/execution-packet.schema.yaml +0 -49
- package/contracts/external-host-compatibility.yaml +0 -22
- package/contracts/forbidden-shortcuts.catalog.yaml +0 -23
- package/contracts/high-risk-admission.schema.yaml +0 -23
- package/contracts/high-risk-execution-result.yaml +0 -20
- package/contracts/orchestration-state.schema.yaml +0 -41
- package/contracts/overflow-continuation.schema.yaml +0 -12
- package/contracts/packet.schema.yaml +0 -30
- package/contracts/pending-note.schema.yaml +0 -17
- package/contracts/prompt.schema.yaml +0 -12
- package/contracts/remediation.schema.yaml +0 -16
- package/contracts/result.schema.yaml +0 -24
- package/contracts/spec-reconstruction-result.yaml +0 -41
- package/contracts/sweep-design-result.yaml +0 -349
- package/contracts/topic-goal.schema.yaml +0 -87
- package/contracts/topic-run-ledger.schema.yaml +0 -72
- package/contracts/topic-step-decision.schema.yaml +0 -45
- package/contracts/topic.schema.yaml +0 -65
- package/contracts/true-close.schema.yaml +0 -15
- package/contracts/wave.schema.yaml +0 -29
- package/contracts/worker-output.schema.yaml +0 -15
- package/contracts/workflow-consumer.schema.yaml +0 -110
- package/methodology/audit-sweep-p0p1-recall.yaml +0 -45
- package/methodology/authority-convergence-policy.yaml +0 -42
- package/methodology/overflow-continuation-policy.yaml +0 -14
- package/methodology/skill-exchange-projection.yaml +0 -114
- package/methodology/skill-handoff.yaml +0 -34
- package/methodology/skill-installer-result.yaml +0 -27
- package/methodology/skill-installer-summary-projection.yaml +0 -181
- package/methodology/skill-runtime.yaml +0 -23
- package/methodology/spec-target-truth-profile.yaml +0 -46
- package/methodology/topic-lifecycle-report.yaml +0 -144
- package/methodology/topic-lifecycle.yaml +0 -37
- package/methodology/topic-naming-ontology.yaml +0 -21
- package/methodology/topic-ontology.yaml +0 -38
- package/methodology/topic-validation-policy.yaml +0 -9
- package/methodology/wave-dag-policy.yaml +0 -14
- package/spec/_meta/command-gating-matrix.yaml +0 -143
- package/spec/_meta/generate-drift-migration-checklist.yaml +0 -137
- package/spec/_meta/governance-routing-cutover-checklist.yaml +0 -35
- package/spec/_meta/phase2-impacted-surface-matrix.yaml +0 -44
- package/spec/_meta/spec-authority-cutover-readiness.yaml +0 -102
- package/spec/bootstrap-state.yaml +0 -99
package/cli/lib/topic-goal.mjs
DELETED
|
@@ -1,440 +0,0 @@
|
|
|
1
|
-
import { createHash } from "node:crypto";
|
|
2
|
-
import { readdir } from "node:fs/promises";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
import { fileURLToPath } from "node:url";
|
|
5
|
-
|
|
6
|
-
import { pathExists, readTextIfFile } from "./fs-helpers.mjs";
|
|
7
|
-
import { loadTopicReport, validateTopicGraph, validateTopicRoot } from "./topic.mjs";
|
|
8
|
-
import { parseYamlText } from "./yaml-helpers.mjs";
|
|
9
|
-
import { loadGovernanceConfig } from "./internal/governance/config.mjs";
|
|
10
|
-
|
|
11
|
-
const PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..");
|
|
12
|
-
const PACKAGE_REF_PREFIX = "package://@nimiplatform/nimi-coding/";
|
|
13
|
-
const TOPIC_GOAL_CONTRACT_REF = `${PACKAGE_REF_PREFIX}contracts/topic-goal.schema.yaml`;
|
|
14
|
-
const HOST_TOPIC_GOAL_CONTRACT_REF = ".nimi/contracts/topic-goal.schema.yaml";
|
|
15
|
-
const FORBIDDEN_SHORTCUTS_REF = `${PACKAGE_REF_PREFIX}contracts/forbidden-shortcuts.catalog.yaml`;
|
|
16
|
-
const HOST_FORBIDDEN_SHORTCUTS_REF = ".nimi/contracts/forbidden-shortcuts.catalog.yaml";
|
|
17
|
-
const GOAL_COMMAND_MAX_CHARS = 1500;
|
|
18
|
-
|
|
19
|
-
const REQUIRED_ARTIFACTS = [
|
|
20
|
-
"topic.yaml",
|
|
21
|
-
"design.md",
|
|
22
|
-
"waves.md",
|
|
23
|
-
"candidate-wave-plan.md",
|
|
24
|
-
"admission-checklists.md",
|
|
25
|
-
"preflight.md",
|
|
26
|
-
"implementation-doctrine.md",
|
|
27
|
-
"manager-session-protocol.md",
|
|
28
|
-
"manager-prompts.md",
|
|
29
|
-
"closeout.md",
|
|
30
|
-
];
|
|
31
|
-
|
|
32
|
-
const REQUIRED_STOP_KEYS = [
|
|
33
|
-
"mvp_subset_contract",
|
|
34
|
-
"legacy_alias",
|
|
35
|
-
"compat_shim",
|
|
36
|
-
"dual_read",
|
|
37
|
-
"dual_write",
|
|
38
|
-
"placeholder_success",
|
|
39
|
-
"happy_path_only_closure",
|
|
40
|
-
"time_phased_layering",
|
|
41
|
-
"app_local_shadow_truth",
|
|
42
|
-
"silent_owner_cut_reopen",
|
|
43
|
-
];
|
|
44
|
-
|
|
45
|
-
const EXECUTION_STAGE_WAVE_STATES = new Set(["preflight_admitted", "implementation_admitted", "implementation_active"]);
|
|
46
|
-
const ADMISSION_READY_WAVE_STATES = new Set(["candidate", "preflight_draft", "needs_revision"]);
|
|
47
|
-
const TERMINAL_WAVE_STATES = new Set(["closed", "retired", "superseded"]);
|
|
48
|
-
const WAVE_ID_PATTERN = /^wave-[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
49
|
-
|
|
50
|
-
const CHECK_REASON = {
|
|
51
|
-
topic_validate_passed: "topic_validate_failed",
|
|
52
|
-
topic_graph_validate_passed: "topic_graph_validate_failed",
|
|
53
|
-
topic_lifecycle_ongoing: "topic_not_ongoing",
|
|
54
|
-
true_close_not_started: "true_close_not_started_required",
|
|
55
|
-
strict_policy_active: "strict_policy_ignored",
|
|
56
|
-
parallel_truth_forbidden: "parallel_truth_not_forbidden",
|
|
57
|
-
profile_resolves: "unknown_profile",
|
|
58
|
-
selected_target_wave_resolves: "selected_target_not_wave",
|
|
59
|
-
selected_wave_single_source: "selected_wave_mismatch",
|
|
60
|
-
wave_option_matches_selected: "wave_override_forbidden",
|
|
61
|
-
selected_wave_executable: "selected_wave_not_executable",
|
|
62
|
-
selected_wave_dependencies_terminal: "dependency_not_terminal",
|
|
63
|
-
selected_wave_goal_present: "missing_primary_closure_goal",
|
|
64
|
-
forbidden_shortcuts_present: "forbidden_shortcuts_incomplete",
|
|
65
|
-
forbidden_shortcuts_catalog_aligned: "forbidden_shortcuts_catalog_drift",
|
|
66
|
-
required_artifacts_present: "required_artifact_missing",
|
|
67
|
-
no_unresolved_placeholders: "unresolved_placeholder",
|
|
68
|
-
stop_line_declared: "stop_line_missing",
|
|
69
|
-
human_gates_declared: "human_gates_missing",
|
|
70
|
-
validation_commands_declared: "validation_commands_missing",
|
|
71
|
-
closeout_criteria_declared: "closeout_criteria_missing",
|
|
72
|
-
authority_owner_declared: "authority_owner_missing",
|
|
73
|
-
work_type_declared: "work_type_missing",
|
|
74
|
-
authority_change_admitted: "authority_alignment_missing",
|
|
75
|
-
host_projection_aligned: "host_projection_drift",
|
|
76
|
-
goal_size_within_limit: "goal_too_large",
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
function toPortableRelativePath(filePath) {
|
|
80
|
-
return filePath.split(path.sep).join("/");
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
function projectRef(projectRoot, absolutePath) {
|
|
84
|
-
return toPortableRelativePath(path.relative(projectRoot, absolutePath));
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
function packagePath(relativePath) {
|
|
88
|
-
return path.join(PACKAGE_ROOT, relativePath.replace(PACKAGE_REF_PREFIX, ""));
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
function check(id, ok, message, extra = {}) {
|
|
92
|
-
return {
|
|
93
|
-
id,
|
|
94
|
-
status: ok ? "pass" : "fail",
|
|
95
|
-
severity: ok ? "info" : "blocking",
|
|
96
|
-
message,
|
|
97
|
-
...extra,
|
|
98
|
-
};
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
function sectionText(markdown, sectionTitle) {
|
|
102
|
-
const escaped = sectionTitle.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
103
|
-
const match = markdown.match(new RegExp(`^##\\s+${escaped}\\s*$([\\s\\S]*?)(?=^##\\s+|$(?![\\s\\S]))`, "im"));
|
|
104
|
-
return match ? match[1].trim() : "";
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
function hasSectionBullet(markdown, sectionTitle) {
|
|
108
|
-
return /^[-*]\s+\S/m.test(sectionText(markdown, sectionTitle));
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
function hasUnresolvedPlaceholder(text) {
|
|
112
|
-
return /(?<![A-Za-z-])(?:TODO|TBD|FIXME|XXX)(?![A-Za-z-])/i.test(text)
|
|
113
|
-
|| /<\s*(?:placeholder|place-holder|fill-me|missing)\s*>/i.test(text)
|
|
114
|
-
|| /\?\?\?/.test(text);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
function normalizeCommandText(command) {
|
|
118
|
-
return String(command ?? "").replace(/\s+/g, " ").trim();
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
function validationCommandScope(command) {
|
|
122
|
-
const scopeMatch = command.match(/(?:^|\s)--scope\s+([^\s]+)/u);
|
|
123
|
-
if (scopeMatch) {
|
|
124
|
-
return scopeMatch[1];
|
|
125
|
-
}
|
|
126
|
-
if (command.includes("topic validate graph")) {
|
|
127
|
-
return "graph";
|
|
128
|
-
}
|
|
129
|
-
if (command.includes("topic validate")) {
|
|
130
|
-
return "topic";
|
|
131
|
-
}
|
|
132
|
-
if (command.includes("test")) {
|
|
133
|
-
return "test";
|
|
134
|
-
}
|
|
135
|
-
return "selected wave";
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
function validationCommandEntry(command) {
|
|
139
|
-
const normalized = normalizeCommandText(command);
|
|
140
|
-
return {
|
|
141
|
-
command: normalized,
|
|
142
|
-
cwd: ".",
|
|
143
|
-
profile: null,
|
|
144
|
-
scope: validationCommandScope(normalized),
|
|
145
|
-
required: true,
|
|
146
|
-
expected_exit_code: 0,
|
|
147
|
-
};
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
function mergeValidationCommandEntries(entries) {
|
|
151
|
-
const seen = new Set();
|
|
152
|
-
const output = [];
|
|
153
|
-
for (const entry of entries) {
|
|
154
|
-
if (!entry.command || seen.has(entry.command)) {
|
|
155
|
-
continue;
|
|
156
|
-
}
|
|
157
|
-
seen.add(entry.command);
|
|
158
|
-
output.push(entry);
|
|
159
|
-
}
|
|
160
|
-
return output;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
function parseValidationCommands(artifactTexts) {
|
|
164
|
-
const commands = [];
|
|
165
|
-
const commandPattern = /\b(?:pnpm|npm|npx|node|go|cargo)\s+[^\n`]+/g;
|
|
166
|
-
for (const { text } of artifactTexts) {
|
|
167
|
-
for (const match of text.matchAll(commandPattern)) {
|
|
168
|
-
const command = normalizeCommandText(match[0].replace(/[.)\]]+$/u, ""));
|
|
169
|
-
if (command.includes("<") || command.includes("topic goal")) {
|
|
170
|
-
continue;
|
|
171
|
-
}
|
|
172
|
-
if (command.length > 0 && !commands.includes(command)) {
|
|
173
|
-
commands.push(command);
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
return commands.map(validationCommandEntry);
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
function selectedWaveValidationCommands(selectedWave) {
|
|
181
|
-
const commands = [
|
|
182
|
-
...(Array.isArray(selectedWave?.validation_commands) ? selectedWave.validation_commands : []),
|
|
183
|
-
...(Array.isArray(selectedWave?.source_sweep_design?.validation_commands) ? selectedWave.source_sweep_design.validation_commands : []),
|
|
184
|
-
];
|
|
185
|
-
return commands
|
|
186
|
-
.map(normalizeCommandText)
|
|
187
|
-
.filter((command) => command.length > 0)
|
|
188
|
-
.map(validationCommandEntry);
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
function parseHumanGates(preflightText) {
|
|
192
|
-
return sectionText(preflightText, "Human Gates")
|
|
193
|
-
.split("\n")
|
|
194
|
-
.map((line) => line.match(/^[-*]\s+(.+)$/)?.[1]?.trim())
|
|
195
|
-
.filter(Boolean);
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
async function resolveProfile(projectRoot, requestedProfile) {
|
|
199
|
-
const governance = await loadGovernanceConfig(projectRoot);
|
|
200
|
-
const hostText = await readTextIfFile(path.join(projectRoot, ".nimi", "config", "host-profile.yaml"))
|
|
201
|
-
?? await readTextIfFile(path.join(PACKAGE_ROOT, "config", "host-profile.yaml"));
|
|
202
|
-
const hostProfile = parseYamlText(hostText)?.host_profile?.id ?? null;
|
|
203
|
-
const defaultProfile = governance.ok ? governance.config.profileId : hostProfile;
|
|
204
|
-
const effectiveProfile = requestedProfile ?? defaultProfile;
|
|
205
|
-
return {
|
|
206
|
-
ok: typeof effectiveProfile === "string"
|
|
207
|
-
&& effectiveProfile.length > 0
|
|
208
|
-
&& effectiveProfile === defaultProfile,
|
|
209
|
-
profile: effectiveProfile,
|
|
210
|
-
defaultProfile,
|
|
211
|
-
};
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
async function loadForbiddenShortcutsCatalog(projectRoot) {
|
|
215
|
-
const canonicalText = await readTextIfFile(packagePath(FORBIDDEN_SHORTCUTS_REF));
|
|
216
|
-
const hostText = await readTextIfFile(path.join(projectRoot, HOST_FORBIDDEN_SHORTCUTS_REF));
|
|
217
|
-
const catalog = parseYamlText(canonicalText);
|
|
218
|
-
const keys = Array.isArray(catalog?.entries)
|
|
219
|
-
? catalog.entries.map((entry) => entry?.key).filter((key) => typeof key === "string")
|
|
220
|
-
: [];
|
|
221
|
-
return {
|
|
222
|
-
keys,
|
|
223
|
-
aligned: hostText === null || hostText === canonicalText,
|
|
224
|
-
};
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
async function loadSourceArtifacts(topicDir) {
|
|
228
|
-
const artifactTexts = [];
|
|
229
|
-
const missing = [];
|
|
230
|
-
for (const fileName of REQUIRED_ARTIFACTS) {
|
|
231
|
-
const artifactPath = path.join(topicDir, fileName);
|
|
232
|
-
const text = await readTextIfFile(artifactPath);
|
|
233
|
-
if (text === null) {
|
|
234
|
-
missing.push(fileName);
|
|
235
|
-
} else {
|
|
236
|
-
artifactTexts.push({ ref: fileName, text });
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
return { artifactTexts, missing };
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
function fileReferencesWave(fileName, waveId) {
|
|
243
|
-
return fileName.includes(waveId) || fileName.includes(waveId.replace(/^wave-/, "wave-"));
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
async function collectLineageArtifacts(topicDir, waveIds) {
|
|
247
|
-
const entries = await readdir(topicDir, { withFileTypes: true });
|
|
248
|
-
return entries
|
|
249
|
-
.filter((entry) => entry.isFile())
|
|
250
|
-
.map((entry) => entry.name)
|
|
251
|
-
.filter((name) => /^(packet|result|closeout)-/.test(name))
|
|
252
|
-
.filter((name) => waveIds.some((waveId) => fileReferencesWave(name, waveId)))
|
|
253
|
-
.sort();
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
function selectedWaveResolution(topic) {
|
|
257
|
-
const waves = Array.isArray(topic.waves) ? topic.waves : [];
|
|
258
|
-
const selectedTarget = typeof topic.selected_next_target === "string" ? topic.selected_next_target : null;
|
|
259
|
-
const matchingWaves = selectedTarget === null ? [] : waves.filter((wave) => wave.wave_id === selectedTarget);
|
|
260
|
-
const selectedWaves = waves.filter((wave) => wave.selected === true);
|
|
261
|
-
const terminalIds = new Set(waves.filter((wave) => TERMINAL_WAVE_STATES.has(wave.state)).map((wave) => wave.wave_id));
|
|
262
|
-
const allWavesTerminal = waves.length > 0 && waves.every((wave) => TERMINAL_WAVE_STATES.has(wave.state));
|
|
263
|
-
const deterministicNextWave = allWavesTerminal ? null : waves.find((wave) => {
|
|
264
|
-
if (TERMINAL_WAVE_STATES.has(wave.state)) return false;
|
|
265
|
-
if (!ADMISSION_READY_WAVE_STATES.has(wave.state)) return false;
|
|
266
|
-
const deps = Array.isArray(wave.deps) ? wave.deps : [];
|
|
267
|
-
return deps.every((dep) => terminalIds.has(dep));
|
|
268
|
-
}) ?? null;
|
|
269
|
-
const selectedWave = matchingWaves.length === 1 ? matchingWaves[0] : null;
|
|
270
|
-
const executionStartWave = selectedWave ?? deterministicNextWave;
|
|
271
|
-
return {
|
|
272
|
-
waves,
|
|
273
|
-
selectedTarget,
|
|
274
|
-
selectedWave,
|
|
275
|
-
executionStartWave,
|
|
276
|
-
deterministicNextWave,
|
|
277
|
-
allWavesTerminal,
|
|
278
|
-
matchingWaveCount: matchingWaves.length,
|
|
279
|
-
selectedWaves,
|
|
280
|
-
};
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
function dependencyEvidence(lineageRefs, depId) {
|
|
284
|
-
return lineageRefs.some((ref) => fileReferencesWave(ref, depId) && /^(result|closeout)-/.test(ref));
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
function buildGoalCommand(topicId, executionStartWave, sourceArtifacts) {
|
|
288
|
-
const artifactList = sourceArtifacts.map((ref) => path.basename(ref)).join(", ");
|
|
289
|
-
const cursorClause = executionStartWave
|
|
290
|
-
? `, starting at execution cursor ${executionStartWave.wave_id}`
|
|
291
|
-
: "";
|
|
292
|
-
return `/goal Execute topic ${topicId} to completion${cursorClause}. This is a topic-level goal: do not mark complete after a single wave closeout. Use nimicoding topic-runner run ${topicId} --run-id <run-id> --adapter codex to advance deterministic wave admission, preflight, implementation, validation, result recording, wave closeout, and next-wave selection. Treat ${artifactList} as the execution contract. Wave closeout is a validation boundary only. Complete only after all waves are terminal and topic true-close/closeout evidence is recorded. Stop only for declared human gates, authority/scope changes, lowered gates, destructive evidence deletion, unresolved blockers, or required contract changes.`;
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
async function checkHostProjection(projectRoot) {
|
|
296
|
-
const canonicalText = await readTextIfFile(packagePath(TOPIC_GOAL_CONTRACT_REF));
|
|
297
|
-
const hostPath = path.join(projectRoot, HOST_TOPIC_GOAL_CONTRACT_REF);
|
|
298
|
-
const hostInfo = await pathExists(hostPath);
|
|
299
|
-
if (!hostInfo) {
|
|
300
|
-
return { ok: true, message: "host topic-goal projection is absent" };
|
|
301
|
-
}
|
|
302
|
-
const hostText = await readTextIfFile(hostPath);
|
|
303
|
-
return {
|
|
304
|
-
ok: canonicalText !== null && hostText === canonicalText,
|
|
305
|
-
message: canonicalText !== null && hostText === canonicalText
|
|
306
|
-
? "host topic-goal schema projection matches the package contract"
|
|
307
|
-
: "host topic-goal schema projection differs from the package contract",
|
|
308
|
-
};
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
function buildStateHash(artifactTexts, extraTexts) {
|
|
312
|
-
const hash = createHash("sha256");
|
|
313
|
-
for (const artifact of [...artifactTexts, ...extraTexts].sort((a, b) => a.ref.localeCompare(b.ref))) {
|
|
314
|
-
hash.update(artifact.ref);
|
|
315
|
-
hash.update("\0");
|
|
316
|
-
hash.update(artifact.text ?? "");
|
|
317
|
-
hash.update("\0");
|
|
318
|
-
}
|
|
319
|
-
return hash.digest("hex");
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
export async function buildTopicGoal(projectRoot, options) {
|
|
323
|
-
const loaded = await loadTopicReport(projectRoot, options.topicInput);
|
|
324
|
-
if (!loaded.ok) {
|
|
325
|
-
return {
|
|
326
|
-
inputError: true,
|
|
327
|
-
ok: false,
|
|
328
|
-
error: loaded.error,
|
|
329
|
-
};
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
const topicReport = await validateTopicRoot(projectRoot, options.topicInput);
|
|
333
|
-
const graphReport = await validateTopicGraph(projectRoot, options.topicInput);
|
|
334
|
-
const profile = await resolveProfile(projectRoot, options.profile);
|
|
335
|
-
const { artifactTexts, missing } = await loadSourceArtifacts(loaded.topicDir);
|
|
336
|
-
const textByRef = new Map(artifactTexts.map((artifact) => [artifact.ref, artifact.text]));
|
|
337
|
-
const resolution = selectedWaveResolution(loaded.topic);
|
|
338
|
-
const lineageWaveIds = [
|
|
339
|
-
...new Set([
|
|
340
|
-
resolution.selectedTarget,
|
|
341
|
-
resolution.executionStartWave?.wave_id,
|
|
342
|
-
...resolution.waves.flatMap((wave) => Array.isArray(wave.deps) ? wave.deps : []),
|
|
343
|
-
].filter(Boolean)),
|
|
344
|
-
];
|
|
345
|
-
const lineageRefs = await collectLineageArtifacts(loaded.topicDir, lineageWaveIds);
|
|
346
|
-
const lineageTexts = [];
|
|
347
|
-
for (const ref of lineageRefs) {
|
|
348
|
-
const text = await readTextIfFile(path.join(loaded.topicDir, ref));
|
|
349
|
-
lineageTexts.push({ ref, text: text ?? "" });
|
|
350
|
-
}
|
|
351
|
-
const sourceArtifacts = [...REQUIRED_ARTIFACTS, ...lineageRefs];
|
|
352
|
-
const sourceArtifactTexts = [...artifactTexts, ...lineageTexts];
|
|
353
|
-
const validationCommands = mergeValidationCommandEntries([
|
|
354
|
-
...selectedWaveValidationCommands(resolution.executionStartWave),
|
|
355
|
-
...parseValidationCommands(sourceArtifactTexts.filter((artifact) => artifact.ref !== "topic.yaml")),
|
|
356
|
-
]);
|
|
357
|
-
const humanGates = parseHumanGates(textByRef.get("preflight.md") ?? "");
|
|
358
|
-
const forbiddenCatalog = await loadForbiddenShortcutsCatalog(projectRoot);
|
|
359
|
-
const hostProjection = await checkHostProjection(projectRoot);
|
|
360
|
-
const executionStartWave = resolution.executionStartWave;
|
|
361
|
-
const deps = Array.isArray(executionStartWave?.deps) ? executionStartWave.deps : [];
|
|
362
|
-
const depFailures = deps.filter((depId) => {
|
|
363
|
-
const depWave = resolution.waves.find((wave) => wave.wave_id === depId);
|
|
364
|
-
return !depWave || !TERMINAL_WAVE_STATES.has(depWave.state) || !dependencyEvidence(lineageRefs, depId);
|
|
365
|
-
});
|
|
366
|
-
const selectedTargetReady = resolution.selectedTarget === null || resolution.selectedTarget === "topic_design_baseline"
|
|
367
|
-
? resolution.deterministicNextWave !== null || resolution.allWavesTerminal
|
|
368
|
-
: resolution.matchingWaveCount === 1 && WAVE_ID_PATTERN.test(resolution.selectedTarget);
|
|
369
|
-
const selectedWaveSourceReady = resolution.selectedTarget === null || resolution.selectedTarget === "topic_design_baseline"
|
|
370
|
-
? resolution.selectedWaves.length === 0
|
|
371
|
-
: resolution.selectedWaves.length === 1 && resolution.selectedWaves[0]?.wave_id === resolution.selectedTarget;
|
|
372
|
-
const executionStartWaveReady = executionStartWave
|
|
373
|
-
? EXECUTION_STAGE_WAVE_STATES.has(executionStartWave.state) || ADMISSION_READY_WAVE_STATES.has(executionStartWave.state)
|
|
374
|
-
: resolution.allWavesTerminal;
|
|
375
|
-
const executionStartGoalPresent = executionStartWave
|
|
376
|
-
? typeof executionStartWave.primary_closure_goal === "string" && executionStartWave.primary_closure_goal.trim().length > 0
|
|
377
|
-
: resolution.allWavesTerminal;
|
|
378
|
-
const commands = validationCommands.map((entry) => entry.command);
|
|
379
|
-
|
|
380
|
-
const checks = [
|
|
381
|
-
check("topic_validate_passed", topicReport.ok === true, topicReport.ok ? "topic validate passes" : topicReport.error ?? "topic validate failed"),
|
|
382
|
-
check("topic_graph_validate_passed", graphReport.ok === true, graphReport.ok ? "topic validate graph passes" : graphReport.error ?? "topic validate graph failed"),
|
|
383
|
-
check("topic_lifecycle_ongoing", loaded.topic.state === "ongoing", `topic state is ${loaded.topic.state ?? "missing"}`),
|
|
384
|
-
check("true_close_not_started", loaded.topic.current_true_close_status === "not_started", `current_true_close_status is ${loaded.topic.current_true_close_status ?? "missing"}`),
|
|
385
|
-
check("strict_policy_active", topicReport.ignoredByPolicy !== true, topicReport.ignoredByPolicy ? "topic root is ignored by strict validation policy" : "topic root is under strict validation policy"),
|
|
386
|
-
check("parallel_truth_forbidden", loaded.topic.parallel_truth === "forbidden", `parallel_truth is ${loaded.topic.parallel_truth ?? "missing"}`),
|
|
387
|
-
check("profile_resolves", profile.ok, profile.ok ? `profile resolves as ${profile.profile}` : `unknown or mismatched profile ${profile.profile ?? "missing"}`),
|
|
388
|
-
check("selected_target_wave_resolves", selectedTargetReady, resolution.selectedTarget ? `selected_next_target is ${resolution.selectedTarget}` : `execution cursor is ${executionStartWave?.wave_id ?? (resolution.allWavesTerminal ? "topic closeout" : "missing")}`),
|
|
389
|
-
check("selected_wave_single_source", selectedWaveSourceReady, `selected waves: ${resolution.selectedWaves.map((wave) => wave.wave_id).join(", ") || "none"}`),
|
|
390
|
-
check("wave_option_matches_selected", options.wave === null || options.wave === executionStartWave?.wave_id, options.wave === null ? "no wave assertion provided" : `--wave is ${options.wave}`),
|
|
391
|
-
check("selected_wave_executable", executionStartWaveReady, executionStartWave ? `execution cursor wave state is ${executionStartWave.state}` : resolution.allWavesTerminal ? "topic is ready for topic closeout" : "execution cursor does not resolve"),
|
|
392
|
-
check("selected_wave_dependencies_terminal", depFailures.length === 0, depFailures.length === 0 ? "selected wave dependencies are terminal by lifecycle evidence" : `dependencies are not terminal by evidence: ${depFailures.join(", ")}`),
|
|
393
|
-
check("selected_wave_goal_present", executionStartGoalPresent, executionStartWave?.primary_closure_goal ? "execution cursor declares primary_closure_goal" : resolution.allWavesTerminal ? "all waves terminal; topic closeout is the next goal" : "execution cursor is missing primary_closure_goal"),
|
|
394
|
-
check("forbidden_shortcuts_present", REQUIRED_STOP_KEYS.every((key) => (loaded.topic.forbidden_shortcuts ?? []).includes(key)) && REQUIRED_STOP_KEYS.every((key) => forbiddenCatalog.keys.includes(key)), "topic forbidden_shortcuts include required package catalog keys"),
|
|
395
|
-
check("forbidden_shortcuts_catalog_aligned", forbiddenCatalog.aligned, forbiddenCatalog.aligned ? "host forbidden-shortcuts projection is aligned or absent" : "host forbidden-shortcuts projection differs from package catalog"),
|
|
396
|
-
check("required_artifacts_present", missing.length === 0, missing.length === 0 ? "all required artifacts are present" : `missing artifacts: ${missing.join(", ")}`),
|
|
397
|
-
check("no_unresolved_placeholders", sourceArtifactTexts.every(({ text }) => !hasUnresolvedPlaceholder(text)), "required artifacts contain no frozen placeholder markers"),
|
|
398
|
-
check("stop_line_declared", hasSectionBullet(textByRef.get("preflight.md") ?? "", "Stop Line"), "preflight.md declares Stop Line bullets"),
|
|
399
|
-
check("human_gates_declared", humanGates.length > 0, humanGates.length > 0 ? "preflight.md declares Human Gates bullets" : "preflight.md does not declare Human Gates bullets"),
|
|
400
|
-
check("validation_commands_declared", commands.some((command) => command.includes("topic validate ")) && commands.some((command) => command.includes("topic validate graph")), "validation command evidence includes topic validate and topic validate graph"),
|
|
401
|
-
check("closeout_criteria_declared", /Wave-1 Closeout Requirements/i.test(textByRef.get("closeout.md") ?? "") && /\bcomplete\b/i.test(textByRef.get("closeout.md") ?? "") && /\bpartial\b/i.test(textByRef.get("closeout.md") ?? "") && (/\bblocked\b/i.test(textByRef.get("closeout.md") ?? "") || /Non-Closure Conditions/i.test(textByRef.get("closeout.md") ?? "") || /Do not close if/i.test(textByRef.get("closeout.md") ?? "")) && /\bpending\b/i.test(textByRef.get("closeout.md") ?? ""), "closeout.md declares selected-wave criteria and complete/partial/blocked/pending states"),
|
|
402
|
-
check("authority_owner_declared", sectionText(textByRef.get("preflight.md") ?? "", "Authority Owner").length > 0, "preflight.md declares Authority Owner"),
|
|
403
|
-
check("work_type_declared", sectionText(textByRef.get("preflight.md") ?? "", "Work Type").length > 0, "preflight.md declares Work Type"),
|
|
404
|
-
check("authority_change_admitted", !/authority ownership changes? (?:are )?in scope/i.test(textByRef.get("preflight.md") ?? "") || /\.nimi\/spec\//.test(textByRef.get("preflight.md") ?? ""), "authority-changing work is either out of scope or cites .nimi/spec alignment"),
|
|
405
|
-
check("host_projection_aligned", hostProjection.ok, hostProjection.message),
|
|
406
|
-
];
|
|
407
|
-
|
|
408
|
-
const preliminaryOk = checks.every((entry) => entry.status === "pass");
|
|
409
|
-
const preliminaryGoal = preliminaryOk ? buildGoalCommand(loaded.topicId, executionStartWave, sourceArtifacts) : null;
|
|
410
|
-
checks.push(check("goal_size_within_limit", preliminaryGoal === null || preliminaryGoal.length <= GOAL_COMMAND_MAX_CHARS, preliminaryGoal === null ? "goal size check skipped until readiness passes" : `goal command length is ${preliminaryGoal.length}`));
|
|
411
|
-
|
|
412
|
-
const readinessOk = checks.every((entry) => entry.status === "pass");
|
|
413
|
-
const goalCommand = readinessOk ? preliminaryGoal : null;
|
|
414
|
-
return {
|
|
415
|
-
ok: readinessOk,
|
|
416
|
-
topic_id: loaded.topicId,
|
|
417
|
-
topic_ref: projectRef(projectRoot, loaded.topicDir),
|
|
418
|
-
topic_state: loaded.topic.state ?? null,
|
|
419
|
-
true_close_status: loaded.topic.current_true_close_status ?? null,
|
|
420
|
-
profile: profile.profile ?? null,
|
|
421
|
-
selected_next_target: loaded.topic.selected_next_target ?? null,
|
|
422
|
-
selected_wave_id: executionStartWave?.wave_id ?? null,
|
|
423
|
-
execution_start_wave_id: executionStartWave?.wave_id ?? null,
|
|
424
|
-
topic_state_hash: buildStateHash(sourceArtifactTexts, [
|
|
425
|
-
{ ref: TOPIC_GOAL_CONTRACT_REF, text: await readTextIfFile(packagePath(TOPIC_GOAL_CONTRACT_REF)) ?? "" },
|
|
426
|
-
]),
|
|
427
|
-
readiness: {
|
|
428
|
-
ok: readinessOk,
|
|
429
|
-
checks,
|
|
430
|
-
},
|
|
431
|
-
goal_command: goalCommand,
|
|
432
|
-
source_artifacts: sourceArtifacts,
|
|
433
|
-
validation_commands: validationCommands,
|
|
434
|
-
human_gates: humanGates,
|
|
435
|
-
refusal_reasons: checks
|
|
436
|
-
.filter((entry) => entry.status === "fail")
|
|
437
|
-
.map((entry) => CHECK_REASON[entry.id])
|
|
438
|
-
.filter(Boolean),
|
|
439
|
-
};
|
|
440
|
-
}
|