@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.
- package/CHANGELOG.md +19 -0
- package/CODE_OF_CONDUCT.md +28 -0
- package/CONTRIBUTING.md +45 -0
- package/README.md +371 -344
- package/README.zh-CN.md +307 -0
- package/SECURITY.md +26 -0
- package/adapters/oh-my-codex/README.md +8 -9
- package/cli/commands/audit-sweep.mjs +10 -10
- package/cli/commands/classify-spec-tree.mjs +5 -0
- package/cli/commands/closeout.mjs +3 -0
- package/cli/commands/generate-spec-derived-docs.mjs +20 -0
- package/cli/commands/generate-spec-migration-plan.mjs +30 -0
- package/cli/commands/start.mjs +5 -1
- package/cli/commands/surface-validator-command.mjs +49 -0
- package/cli/commands/sweep-design.mjs +295 -0
- package/cli/commands/sweep.mjs +22 -0
- package/cli/commands/sync.mjs +132 -0
- package/cli/commands/topic-formatters.mjs +8 -8
- package/cli/commands/validate-ai-governance.mjs +167 -46
- package/cli/commands/validate-domain-admission.mjs +5 -0
- package/cli/commands/validate-guidance-bodies.mjs +5 -0
- package/cli/commands/validate-placement.mjs +5 -0
- package/cli/commands/validate-projection-edges.mjs +5 -0
- package/cli/commands/validate-spec-audit.mjs +5 -1
- package/cli/commands/validate-table-family.mjs +5 -0
- package/cli/commands/validate-tracked-output-admission.mjs +5 -0
- package/cli/constants.mjs +5 -49
- package/cli/help.mjs +33 -11
- package/cli/index.mjs +20 -2
- package/cli/lib/audit-sweep-runtime/admissions.mjs +38 -29
- package/cli/lib/audit-sweep-runtime/audit-validity.mjs +8 -0
- package/cli/lib/audit-sweep-runtime/chunks.mjs +11 -11
- package/cli/lib/audit-sweep-runtime/closeout.mjs +8 -8
- package/cli/lib/audit-sweep-runtime/codex-auditor-evidence.mjs +3 -3
- package/cli/lib/audit-sweep-runtime/codex-auditor.mjs +10 -10
- package/cli/lib/audit-sweep-runtime/common.mjs +7 -7
- package/cli/lib/audit-sweep-runtime/format.mjs +3 -3
- package/cli/lib/audit-sweep-runtime/ingest.mjs +8 -8
- package/cli/lib/audit-sweep-runtime/inventory-spec-chunks.mjs +24 -27
- package/cli/lib/audit-sweep-runtime/inventory.mjs +58 -18
- package/cli/lib/audit-sweep-runtime/ledger.mjs +1 -1
- package/cli/lib/audit-sweep-runtime/p0p1-profile.mjs +2 -2
- package/cli/lib/audit-sweep-runtime/remediation.mjs +6 -6
- package/cli/lib/audit-sweep-runtime/rerun.mjs +6 -6
- package/cli/lib/audit-sweep-runtime/status.mjs +1 -1
- package/cli/lib/audit-sweep-runtime/validators.mjs +2 -2
- package/cli/lib/authority-convergence.mjs +397 -2
- package/cli/lib/blueprint-audit.mjs +5 -5
- package/cli/lib/closeout.mjs +126 -3
- package/cli/lib/contracts.mjs +21 -17
- package/cli/lib/handoff.mjs +29 -11
- package/cli/lib/high-risk-admission.mjs +60 -11
- package/cli/lib/high-risk-decision.mjs +31 -2
- package/cli/lib/high-risk-ingest.mjs +5 -1
- package/cli/lib/high-risk-review.mjs +5 -1
- package/cli/lib/internal/contracts-parse.mjs +195 -24
- package/cli/lib/internal/contracts-validators.mjs +3 -2
- package/cli/lib/internal/doctor-bootstrap-surface.mjs +82 -35
- package/cli/lib/internal/doctor-delegated-surface.mjs +1 -1
- package/cli/lib/internal/doctor-finalize.mjs +12 -8
- package/cli/lib/internal/doctor-inspectors.mjs +34 -1
- package/cli/lib/internal/governance/ai/ai-context-budget-core.mjs +74 -12
- package/cli/lib/internal/governance/ai/ai-structure-budget-core.mjs +24 -6
- package/cli/lib/internal/governance/ai/check-agents-freshness.mjs +18 -23
- package/cli/lib/internal/surface-taxonomy-validators.mjs +931 -0
- package/cli/lib/internal/validators-spec.mjs +229 -20
- package/cli/lib/sweep-design-runtime/common.mjs +246 -0
- package/cli/lib/sweep-design-runtime/engine.mjs +733 -0
- package/cli/lib/sweep-design-runtime/fix-topic.mjs +414 -0
- package/cli/lib/sweep-design-runtime/lifecycle.mjs +54 -0
- package/cli/lib/sweep-design-runtime/results.mjs +324 -0
- package/cli/lib/sweep-design.mjs +8 -0
- package/cli/lib/sync.mjs +143 -0
- package/cli/lib/topic-artifacts.mjs +186 -0
- package/cli/lib/topic-authority-coverage.mjs +73 -0
- package/cli/lib/topic-closeout.mjs +560 -0
- package/cli/lib/topic-common.mjs +404 -0
- package/cli/lib/topic-decisions.mjs +332 -0
- package/cli/lib/topic-draft-packets.mjs +126 -7
- package/cli/lib/topic-execution.mjs +515 -0
- package/cli/lib/topic-goal.mjs +112 -33
- package/cli/lib/topic-ledger.mjs +281 -0
- package/cli/lib/topic-lifecycle-artifacts.mjs +173 -0
- package/cli/lib/topic-root-validation.mjs +288 -0
- package/cli/lib/topic-runner-commands.mjs +174 -0
- package/cli/lib/topic-runner-deferral.mjs +532 -0
- package/cli/lib/topic-runner-stale-gates.mjs +114 -0
- package/cli/lib/topic-runner-validation.mjs +138 -0
- package/cli/lib/topic-runner.mjs +109 -154
- package/cli/lib/topic-scaffold.mjs +252 -0
- package/cli/lib/topic-waves.mjs +403 -0
- package/cli/lib/topic.mjs +81 -93
- package/cli/lib/value-helpers.mjs +6 -1
- package/cli/seeds/bootstrap.mjs +96 -20
- package/cli/seeds/seed-policy.yaml +67 -0
- package/config/bootstrap.yaml +1 -1
- package/config/skill-manifest.yaml +4 -2
- package/config/spec-generation-inputs.yaml +41 -19
- package/contracts/audit-remediation-map.schema.yaml +1 -0
- package/contracts/audit-sweep-result.yaml +4 -0
- package/contracts/domain-admission.schema.yaml +56 -0
- package/contracts/migration-inventory.schema.yaml +80 -0
- package/contracts/negative-fixtures.yaml +91 -0
- package/contracts/placement-contract.schema.yaml +163 -0
- package/contracts/projection-edge.schema.yaml +130 -0
- package/contracts/shared-enums.yaml +68 -0
- package/contracts/spec-generation-audit.schema.yaml +19 -4
- package/contracts/spec-generation-inputs.schema.yaml +130 -29
- package/contracts/spec-reconstruction-result.yaml +9 -5
- package/contracts/surface-taxonomy.schema.yaml +201 -0
- package/contracts/sweep-design-result.yaml +349 -0
- package/contracts/table-family.schema.yaml +121 -0
- package/contracts/topic-goal.schema.yaml +10 -1
- package/contracts/tracked-output-admission.schema.yaml +70 -0
- package/contracts/workflow-consumer.schema.yaml +112 -0
- package/methodology/audit-sweep-p0p1-recall.yaml +1 -1
- package/methodology/spec-reconstruction.yaml +53 -30
- package/package.json +19 -4
- package/spec/_meta/command-gating-matrix.yaml +33 -0
- package/spec/_meta/generate-drift-migration-checklist.yaml +44 -62
- package/spec/_meta/governance-routing-cutover-checklist.yaml +3 -3
- package/spec/_meta/phase2-impacted-surface-matrix.yaml +14 -14
- package/spec/_meta/spec-authority-cutover-readiness.yaml +3 -5
- package/spec/_meta/spec-tree-model.yaml +104 -36
- package/spec/bootstrap-state.yaml +36 -36
- package/spec/product-scope.yaml +13 -10
|
@@ -39,6 +39,7 @@ const PACKAGE_REPO_ROOT = fileURLToPath(new URL("../../..", import.meta.url));
|
|
|
39
39
|
|
|
40
40
|
export async function finalizeDoctorState(projectRoot, bootstrapSurface, delegatedSurface) {
|
|
41
41
|
const checks = [...bootstrapSurface.checks, ...delegatedSurface.checks];
|
|
42
|
+
const usesV2SurfaceModel = bootstrapSurface.specGenerationInputs?.mode === "class_filtered";
|
|
42
43
|
|
|
43
44
|
const specContract = await loadSpecReconstructionContract(projectRoot);
|
|
44
45
|
const auditContract = await loadDocSpecAuditContract(projectRoot);
|
|
@@ -170,7 +171,7 @@ export async function finalizeDoctorState(projectRoot, bootstrapSurface, delegat
|
|
|
170
171
|
),
|
|
171
172
|
);
|
|
172
173
|
|
|
173
|
-
const lifecycleAligned = (
|
|
174
|
+
const lifecycleAligned = usesV2SurfaceModel || (
|
|
174
175
|
bootstrapSurface.bootstrapStateContract.treeState === "bootstrap_only"
|
|
175
176
|
&& bootstrapSurface.canonicalTree.ready === false
|
|
176
177
|
) || (
|
|
@@ -187,7 +188,9 @@ export async function finalizeDoctorState(projectRoot, bootstrapSurface, delegat
|
|
|
187
188
|
buildCheck(
|
|
188
189
|
"bootstrap_lifecycle_alignment",
|
|
189
190
|
lifecycleAligned,
|
|
190
|
-
|
|
191
|
+
usesV2SurfaceModel
|
|
192
|
+
? "v2 host-local surface model does not require bootstrap-state lifecycle alignment"
|
|
193
|
+
: lifecycleAligned
|
|
191
194
|
? `bootstrap-state lifecycle ${bootstrapSurface.bootstrapStateContract.treeState ?? "unknown"} is aligned with the current canonical tree readiness`
|
|
192
195
|
: bootstrapSurface.bootstrapStateContract.treeState === "canonical_tree_ready"
|
|
193
196
|
? "bootstrap-state declares canonical_tree_ready but required canonical files are still missing"
|
|
@@ -203,7 +206,8 @@ export async function finalizeDoctorState(projectRoot, bootstrapSurface, delegat
|
|
|
203
206
|
detail: auditArtifact.reason,
|
|
204
207
|
});
|
|
205
208
|
|
|
206
|
-
const auditArtifactConsistent =
|
|
209
|
+
const auditArtifactConsistent = usesV2SurfaceModel
|
|
210
|
+
|| !auditArtifact.present
|
|
207
211
|
|| auditArtifact.outcome !== "completed"
|
|
208
212
|
|| bootstrapSurface.canonicalTree.requiredFilesValid;
|
|
209
213
|
checks.push(
|
|
@@ -268,7 +272,7 @@ export async function finalizeDoctorState(projectRoot, bootstrapSurface, delegat
|
|
|
268
272
|
&& delegatedSurface.contractRuntimeOwnersAligned
|
|
269
273
|
&& delegatedSurface.delegatedModeAligned
|
|
270
274
|
&& delegatedSurface.selfHostedAligned
|
|
271
|
-
&& bootstrapSurface.completionTruth.ok
|
|
275
|
+
&& (usesV2SurfaceModel || bootstrapSurface.completionTruth.ok)
|
|
272
276
|
&& delegatedSurface.resultContractAlignment
|
|
273
277
|
&& delegatedSurface.adapterSelectionValid
|
|
274
278
|
&& adapterProfilesValid
|
|
@@ -281,12 +285,12 @@ export async function finalizeDoctorState(projectRoot, bootstrapSurface, delegat
|
|
|
281
285
|
&& highRiskAdmissionsTruthValid
|
|
282
286
|
&& externalExecutionArtifacts.ok
|
|
283
287
|
&& packageBoundaryTruthOk
|
|
284
|
-
&& bootstrapSurface.specTreeModel.ok
|
|
288
|
+
&& (usesV2SurfaceModel || bootstrapSurface.specTreeModel.ok)
|
|
285
289
|
&& bootstrapSurface.specGenerationInputsContract.ok
|
|
286
290
|
&& bootstrapSurface.specGenerationAuditContract.ok
|
|
287
291
|
&& bootstrapSurface.specGenerationInputs.ok
|
|
288
292
|
&& (!bootstrapSurface.canonicalTree.requiredFilesValid || bootstrapSurface.specGenerationAudit.ok)
|
|
289
|
-
&& bootstrapSurface.commandGatingMatrix.ok
|
|
293
|
+
&& (usesV2SurfaceModel || bootstrapSurface.commandGatingMatrix.ok)
|
|
290
294
|
&& bootstrapSurface.blueprintReferenceAligned
|
|
291
295
|
&& lifecycleAligned,
|
|
292
296
|
requiredContextOrder: delegatedSurface.handoffRequiredContext,
|
|
@@ -304,7 +308,7 @@ export async function finalizeDoctorState(projectRoot, bootstrapSurface, delegat
|
|
|
304
308
|
nextSteps.push("Run `nimicoding blueprint-audit --write-local` after canonical tree generation when a benchmark blueprint is declared.");
|
|
305
309
|
}
|
|
306
310
|
if (bootstrapSurface.canonicalTree.requiredFilesValid && !bootstrapSurface.specGenerationAudit.ok) {
|
|
307
|
-
nextSteps.push("Run `nimicoding validate-spec-audit` after generating
|
|
311
|
+
nextSteps.push("Run `nimicoding validate-spec-audit` after generating the local spec generation audit for the canonical tree.");
|
|
308
312
|
}
|
|
309
313
|
if (!auditArtifact.present && bootstrapSurface.canonicalTree.requiredFilesValid) {
|
|
310
314
|
nextSteps.push("Run `nimicoding handoff --skill doc_spec_audit` and close out the result locally when the audit is complete.");
|
|
@@ -328,7 +332,7 @@ export async function finalizeDoctorState(projectRoot, bootstrapSurface, delegat
|
|
|
328
332
|
})),
|
|
329
333
|
};
|
|
330
334
|
|
|
331
|
-
const completionStatus = !bootstrapSurface.completionTruth.ok || !packageBoundaryTruthOk
|
|
335
|
+
const completionStatus = (!usesV2SurfaceModel && !bootstrapSurface.completionTruth.ok) || !packageBoundaryTruthOk
|
|
332
336
|
? STANDALONE_COMPLETION_STATUS.DRIFTED
|
|
333
337
|
: !hasErrors
|
|
334
338
|
? STANDALONE_COMPLETION_STATUS.COMPLETE
|
|
@@ -265,7 +265,40 @@ export function inspectBootstrapStateContract(bootstrapStateText) {
|
|
|
265
265
|
|
|
266
266
|
export async function inspectCanonicalTree(projectRoot, specTreeModel) {
|
|
267
267
|
if (!specTreeModel.ok) {
|
|
268
|
-
|
|
268
|
+
const reconstructionText = await readTextIfFile(path.join(projectRoot, ".nimi", "methodology", "spec-reconstruction.yaml"));
|
|
269
|
+
const reconstruction = parseYamlText(reconstructionText);
|
|
270
|
+
const requiredFiles = Array.isArray(reconstruction?.reconstruction?.target_tree_shape?.minimal_required_outputs)
|
|
271
|
+
? reconstruction.reconstruction.target_tree_shape.minimal_required_outputs.map(String)
|
|
272
|
+
: [];
|
|
273
|
+
const canonicalRoot = typeof reconstruction?.reconstruction?.target_root === "string"
|
|
274
|
+
? reconstruction.reconstruction.target_root
|
|
275
|
+
: ".nimi/spec";
|
|
276
|
+
const present = [];
|
|
277
|
+
const missing = [];
|
|
278
|
+
|
|
279
|
+
for (const relativePath of requiredFiles) {
|
|
280
|
+
const info = await pathExists(path.join(projectRoot, relativePath));
|
|
281
|
+
if (info && info.isFile()) {
|
|
282
|
+
present.push(relativePath);
|
|
283
|
+
} else {
|
|
284
|
+
missing.push(relativePath);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
if (requiredFiles.length === 0) {
|
|
289
|
+
return emptyCanonicalTree();
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
return {
|
|
293
|
+
profile: "surface_taxonomy_v1",
|
|
294
|
+
canonicalRoot,
|
|
295
|
+
requiredFiles,
|
|
296
|
+
present,
|
|
297
|
+
missing,
|
|
298
|
+
invalid: [],
|
|
299
|
+
requiredFilesValid: missing.length === 0,
|
|
300
|
+
ready: missing.length === 0,
|
|
301
|
+
};
|
|
269
302
|
}
|
|
270
303
|
|
|
271
304
|
const requiredFiles = specTreeModel.requiredFilesByProfile[specTreeModel.profile] ?? [];
|
|
@@ -4,6 +4,9 @@ import path from 'node:path';
|
|
|
4
4
|
import { execSync } from 'node:child_process';
|
|
5
5
|
import YAML from 'yaml';
|
|
6
6
|
|
|
7
|
+
const GOVERNANCE_CONFIG_RELATIVE_PATH = '.nimi/config/governance.yaml';
|
|
8
|
+
const GOVERNANCE_CONTEXT_BUDGET_SECTION = 'ai_governance.context_budget';
|
|
9
|
+
|
|
7
10
|
function escapeRegex(input) {
|
|
8
11
|
return input.replace(/[|\\{}()[\]^$+?.]/g, '\\$&');
|
|
9
12
|
}
|
|
@@ -89,21 +92,50 @@ function parseDateMaybe(input) {
|
|
|
89
92
|
return parsed;
|
|
90
93
|
}
|
|
91
94
|
|
|
92
|
-
function
|
|
95
|
+
function measureLineShape(buffer) {
|
|
93
96
|
if (!buffer || buffer.length === 0) {
|
|
94
|
-
return
|
|
97
|
+
return {
|
|
98
|
+
lines: 0,
|
|
99
|
+
maxLineBytes: 0,
|
|
100
|
+
averageLineBytes: 0,
|
|
101
|
+
};
|
|
95
102
|
}
|
|
103
|
+
|
|
96
104
|
let lines = 1;
|
|
105
|
+
let currentLineBytes = 0;
|
|
106
|
+
let maxLineBytes = 0;
|
|
107
|
+
|
|
97
108
|
for (const byte of buffer) {
|
|
98
109
|
if (byte === 10) {
|
|
110
|
+
maxLineBytes = Math.max(maxLineBytes, currentLineBytes);
|
|
111
|
+
currentLineBytes = 0;
|
|
99
112
|
lines += 1;
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
currentLineBytes += 1;
|
|
116
|
+
}
|
|
117
|
+
maxLineBytes = Math.max(maxLineBytes, currentLineBytes);
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
lines,
|
|
121
|
+
maxLineBytes,
|
|
122
|
+
averageLineBytes: buffer.byteLength / lines,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function getPathSection(source, sectionPath) {
|
|
127
|
+
let current = source;
|
|
128
|
+
for (const segment of sectionPath.split('.')) {
|
|
129
|
+
if (!current || typeof current !== 'object') {
|
|
130
|
+
return null;
|
|
100
131
|
}
|
|
132
|
+
current = current[segment];
|
|
101
133
|
}
|
|
102
|
-
return
|
|
134
|
+
return current && typeof current === 'object' ? current : null;
|
|
103
135
|
}
|
|
104
136
|
|
|
105
|
-
function loadBudgetConfig(cwd, relativePath, inlineConfig, configPathLabel) {
|
|
106
|
-
const
|
|
137
|
+
function loadBudgetConfig(cwd, relativePath, inlineConfig, configPathLabel, configSection) {
|
|
138
|
+
const rawParsed = inlineConfig ?? (() => {
|
|
107
139
|
const configPath = path.join(cwd, relativePath);
|
|
108
140
|
if (!fs.existsSync(configPath)) {
|
|
109
141
|
throw new Error(`budget config not found: ${relativePath}`);
|
|
@@ -111,18 +143,19 @@ function loadBudgetConfig(cwd, relativePath, inlineConfig, configPathLabel) {
|
|
|
111
143
|
const raw = fs.readFileSync(configPath, 'utf8');
|
|
112
144
|
return YAML.parse(raw);
|
|
113
145
|
})();
|
|
146
|
+
const parsed = configSection ? getPathSection(rawParsed, configSection) : rawParsed;
|
|
114
147
|
if (!parsed || typeof parsed !== 'object') {
|
|
115
|
-
throw new Error(`invalid budget config format: ${relativePath}`);
|
|
148
|
+
throw new Error(`invalid budget config format: ${relativePath}${configSection ? `#${configSection}` : ''}`);
|
|
116
149
|
}
|
|
117
150
|
if (!parsed.profiles || typeof parsed.profiles !== 'object') {
|
|
118
|
-
throw new Error(`budget config missing profiles: ${relativePath}`);
|
|
151
|
+
throw new Error(`budget config missing profiles: ${relativePath}${configSection ? `#${configSection}` : ''}`);
|
|
119
152
|
}
|
|
120
153
|
const defaultProfile = String(parsed.default_profile || 'production');
|
|
121
154
|
if (!parsed.profiles[defaultProfile]) {
|
|
122
155
|
throw new Error(`default profile not found in profiles: ${defaultProfile}`);
|
|
123
156
|
}
|
|
124
157
|
return {
|
|
125
|
-
configPath: configPathLabel || relativePath,
|
|
158
|
+
configPath: configPathLabel || (configSection ? `${relativePath}#${configSection}` : relativePath),
|
|
126
159
|
parsed,
|
|
127
160
|
defaultProfile,
|
|
128
161
|
};
|
|
@@ -174,12 +207,15 @@ function waiverAllowedForProfile(profileId) {
|
|
|
174
207
|
|
|
175
208
|
export function evaluateAiContextBudget(options = {}) {
|
|
176
209
|
const cwd = options.cwd || process.cwd();
|
|
177
|
-
const configRelativePath = options.configRelativePath ||
|
|
210
|
+
const configRelativePath = options.configRelativePath || GOVERNANCE_CONFIG_RELATIVE_PATH;
|
|
211
|
+
const configSection = options.configSection
|
|
212
|
+
?? (options.config || options.configRelativePath ? null : GOVERNANCE_CONTEXT_BUDGET_SECTION);
|
|
178
213
|
const { parsed, defaultProfile, configPath } = loadBudgetConfig(
|
|
179
214
|
cwd,
|
|
180
215
|
configRelativePath,
|
|
181
216
|
options.config || null,
|
|
182
217
|
options.configPathLabel || null,
|
|
218
|
+
configSection,
|
|
183
219
|
);
|
|
184
220
|
|
|
185
221
|
const excludeMatchers = compileMatchers(parsed.exclude || []);
|
|
@@ -247,12 +283,27 @@ export function evaluateAiContextBudget(options = {}) {
|
|
|
247
283
|
}
|
|
248
284
|
|
|
249
285
|
const buffer = fs.readFileSync(absolutePath);
|
|
250
|
-
const lines =
|
|
286
|
+
const { lines, maxLineBytes, averageLineBytes } = measureLineShape(buffer);
|
|
251
287
|
const bytes = buffer.byteLength;
|
|
252
288
|
|
|
253
289
|
const linesSeverity = toSeverity(lines, profile.warning_lines, profile.error_lines);
|
|
254
290
|
const bytesSeverity = toSeverity(bytes, profile.warning_bytes, profile.error_bytes);
|
|
255
|
-
const
|
|
291
|
+
const maxLineBytesSeverity = toSeverity(
|
|
292
|
+
maxLineBytes,
|
|
293
|
+
profile.warning_max_line_bytes,
|
|
294
|
+
profile.error_max_line_bytes,
|
|
295
|
+
);
|
|
296
|
+
const averageLineBytesSeverity = toSeverity(
|
|
297
|
+
averageLineBytes,
|
|
298
|
+
profile.warning_average_line_bytes,
|
|
299
|
+
profile.error_average_line_bytes,
|
|
300
|
+
);
|
|
301
|
+
const severity = [
|
|
302
|
+
linesSeverity,
|
|
303
|
+
bytesSeverity,
|
|
304
|
+
maxLineBytesSeverity,
|
|
305
|
+
averageLineBytesSeverity,
|
|
306
|
+
].reduce((current, next) => maxSeverity(current, next), "none");
|
|
256
307
|
|
|
257
308
|
const waiver = waiverMap.get(relativePath) || null;
|
|
258
309
|
const waiverExpired = waiver?.until ? waiver.until.getTime() < Date.now() : false;
|
|
@@ -263,13 +314,21 @@ export function evaluateAiContextBudget(options = {}) {
|
|
|
263
314
|
profile: profileId,
|
|
264
315
|
lines,
|
|
265
316
|
bytes,
|
|
317
|
+
maxLineBytes,
|
|
318
|
+
averageLineBytes,
|
|
266
319
|
severity,
|
|
267
320
|
linesSeverity,
|
|
268
321
|
bytesSeverity,
|
|
322
|
+
maxLineBytesSeverity,
|
|
323
|
+
averageLineBytesSeverity,
|
|
269
324
|
warningLines: profile.warning_lines,
|
|
270
325
|
errorLines: profile.error_lines,
|
|
271
326
|
warningBytes: profile.warning_bytes,
|
|
272
327
|
errorBytes: profile.error_bytes,
|
|
328
|
+
warningMaxLineBytes: profile.warning_max_line_bytes,
|
|
329
|
+
errorMaxLineBytes: profile.error_max_line_bytes,
|
|
330
|
+
warningAverageLineBytes: profile.warning_average_line_bytes,
|
|
331
|
+
errorAverageLineBytes: profile.error_average_line_bytes,
|
|
273
332
|
waiver,
|
|
274
333
|
waiverExpired,
|
|
275
334
|
waived,
|
|
@@ -285,7 +344,10 @@ export function evaluateAiContextBudget(options = {}) {
|
|
|
285
344
|
if (right.lines !== left.lines) {
|
|
286
345
|
return right.lines - left.lines;
|
|
287
346
|
}
|
|
288
|
-
|
|
347
|
+
if (right.bytes !== left.bytes) {
|
|
348
|
+
return right.bytes - left.bytes;
|
|
349
|
+
}
|
|
350
|
+
return right.maxLineBytes - left.maxLineBytes;
|
|
289
351
|
});
|
|
290
352
|
|
|
291
353
|
return {
|
|
@@ -4,6 +4,9 @@ import path from 'node:path';
|
|
|
4
4
|
import { execSync } from 'node:child_process';
|
|
5
5
|
import YAML from 'yaml';
|
|
6
6
|
|
|
7
|
+
const GOVERNANCE_CONFIG_RELATIVE_PATH = '.nimi/config/governance.yaml';
|
|
8
|
+
const GOVERNANCE_STRUCTURE_BUDGET_SECTION = 'ai_governance.structure_budget';
|
|
9
|
+
|
|
7
10
|
function escapeRegex(input) {
|
|
8
11
|
return input.replace(/[|\\{}()[\]^$+?.]/g, '\\$&');
|
|
9
12
|
}
|
|
@@ -77,8 +80,19 @@ function parseDateMaybe(input) {
|
|
|
77
80
|
return parsed;
|
|
78
81
|
}
|
|
79
82
|
|
|
80
|
-
function
|
|
81
|
-
|
|
83
|
+
function getPathSection(source, sectionPath) {
|
|
84
|
+
let current = source;
|
|
85
|
+
for (const segment of sectionPath.split('.')) {
|
|
86
|
+
if (!current || typeof current !== 'object') {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
current = current[segment];
|
|
90
|
+
}
|
|
91
|
+
return current && typeof current === 'object' ? current : null;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function loadBudgetConfig(cwd, relativePath, inlineConfig, configPathLabel, configSection) {
|
|
95
|
+
const rawParsed = inlineConfig ?? (() => {
|
|
82
96
|
const configPath = path.join(cwd, relativePath);
|
|
83
97
|
if (!fs.existsSync(configPath)) {
|
|
84
98
|
throw new Error(`budget config not found: ${relativePath}`);
|
|
@@ -86,14 +100,15 @@ function loadBudgetConfig(cwd, relativePath, inlineConfig, configPathLabel) {
|
|
|
86
100
|
const raw = fs.readFileSync(configPath, 'utf8');
|
|
87
101
|
return YAML.parse(raw);
|
|
88
102
|
})();
|
|
103
|
+
const parsed = configSection ? getPathSection(rawParsed, configSection) : rawParsed;
|
|
89
104
|
if (!parsed || typeof parsed !== 'object') {
|
|
90
|
-
throw new Error(`invalid budget config format: ${relativePath}`);
|
|
105
|
+
throw new Error(`invalid budget config format: ${relativePath}${configSection ? `#${configSection}` : ''}`);
|
|
91
106
|
}
|
|
92
107
|
if (!Array.isArray(parsed.rules) || parsed.rules.length === 0) {
|
|
93
|
-
throw new Error(`budget config missing rules: ${relativePath}`);
|
|
108
|
+
throw new Error(`budget config missing rules: ${relativePath}${configSection ? `#${configSection}` : ''}`);
|
|
94
109
|
}
|
|
95
110
|
return {
|
|
96
|
-
configPath: configPathLabel || relativePath,
|
|
111
|
+
configPath: configPathLabel || (configSection ? `${relativePath}#${configSection}` : relativePath),
|
|
97
112
|
parsed,
|
|
98
113
|
};
|
|
99
114
|
}
|
|
@@ -244,12 +259,15 @@ function compareRows(left, right) {
|
|
|
244
259
|
|
|
245
260
|
export function evaluateAiStructureBudget(options = {}) {
|
|
246
261
|
const cwd = options.cwd || process.cwd();
|
|
247
|
-
const configRelativePath = options.configRelativePath ||
|
|
262
|
+
const configRelativePath = options.configRelativePath || GOVERNANCE_CONFIG_RELATIVE_PATH;
|
|
263
|
+
const configSection = options.configSection
|
|
264
|
+
?? (options.config || options.configRelativePath ? null : GOVERNANCE_STRUCTURE_BUDGET_SECTION);
|
|
248
265
|
const { parsed, configPath } = loadBudgetConfig(
|
|
249
266
|
cwd,
|
|
250
267
|
configRelativePath,
|
|
251
268
|
options.config || null,
|
|
252
269
|
options.configPathLabel || null,
|
|
270
|
+
configSection,
|
|
253
271
|
);
|
|
254
272
|
const excludeMatchers = compileMatchers(parsed.exclude || []);
|
|
255
273
|
const compiledRules = (parsed.rules || []).map((rule) => ({
|
|
@@ -2,12 +2,7 @@ import * as fs from "node:fs";
|
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
|
|
4
4
|
const DEFAULT_TARGETS = [
|
|
5
|
-
{ rel: "AGENTS.md", maxLines:
|
|
6
|
-
{ rel: "nimi-backend/AGENTS.md", maxLines: 80 },
|
|
7
|
-
{ rel: "nimi-dashboard/AGENTS.md", maxLines: 65 },
|
|
8
|
-
{ rel: ".nimi/spec/AGENTS.md", maxLines: 70 },
|
|
9
|
-
{ rel: "scripts/AGENTS.md", maxLines: 45 },
|
|
10
|
-
{ rel: "tests/AGENTS.md", maxLines: 35 },
|
|
5
|
+
{ rel: "AGENTS.md", maxLines: 120 },
|
|
11
6
|
];
|
|
12
7
|
|
|
13
8
|
const DEFAULT_REQUIRED_SECTIONS = [
|
|
@@ -17,7 +12,7 @@ const DEFAULT_REQUIRED_SECTIONS = [
|
|
|
17
12
|
"## Verification Commands",
|
|
18
13
|
];
|
|
19
14
|
|
|
20
|
-
const DEFAULT_STALE_TOKENS = [
|
|
15
|
+
const DEFAULT_STALE_TOKENS = [];
|
|
21
16
|
|
|
22
17
|
const GENERIC_PNPM_COMMANDS = new Set([
|
|
23
18
|
"install",
|
|
@@ -39,18 +34,7 @@ function readJson(filePath) {
|
|
|
39
34
|
|
|
40
35
|
function collectKnownPnpmScripts(projectRoot) {
|
|
41
36
|
const rootPkg = readJson(path.join(projectRoot, "package.json"));
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
for (const rel of ["nimi-backend/package.json", "nimi-dashboard/package.json"]) {
|
|
45
|
-
const abs = path.join(projectRoot, rel);
|
|
46
|
-
if (!fs.existsSync(abs)) continue;
|
|
47
|
-
const pkg = readJson(abs);
|
|
48
|
-
for (const name of Object.keys(pkg.scripts || {})) {
|
|
49
|
-
scripts.add(name);
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
return scripts;
|
|
37
|
+
return new Set(Object.keys(rootPkg.scripts || {}));
|
|
54
38
|
}
|
|
55
39
|
|
|
56
40
|
function validatePnpmCommand(command, knownScripts, failures, relPath) {
|
|
@@ -93,7 +77,7 @@ function validatePathToken(token, failures, relPath, agentsDir, projectRoot) {
|
|
|
93
77
|
}
|
|
94
78
|
}
|
|
95
79
|
|
|
96
|
-
export function
|
|
80
|
+
export function evaluateAgentsFreshnessCheck(options = {}) {
|
|
97
81
|
const projectRoot = options.projectRoot || process.cwd();
|
|
98
82
|
const config = options.config || {};
|
|
99
83
|
const targets = Array.isArray(config.targets) && config.targets.length > 0
|
|
@@ -143,13 +127,24 @@ export function runAgentsFreshnessCheck(options = {}) {
|
|
|
143
127
|
}
|
|
144
128
|
}
|
|
145
129
|
|
|
146
|
-
|
|
130
|
+
return {
|
|
131
|
+
targets,
|
|
132
|
+
requiredSections,
|
|
133
|
+
staleTokens,
|
|
134
|
+
failures,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export function runAgentsFreshnessCheck(options = {}) {
|
|
139
|
+
const report = evaluateAgentsFreshnessCheck(options);
|
|
140
|
+
|
|
141
|
+
if (report.failures.length > 0) {
|
|
147
142
|
process.stderr.write(
|
|
148
|
-
`agents freshness check failed:\n${failures.map((entry) => `- ${entry}`).join("\n")}\n`,
|
|
143
|
+
`agents freshness check failed:\n${report.failures.map((entry) => `- ${entry}`).join("\n")}\n`,
|
|
149
144
|
);
|
|
150
145
|
return 1;
|
|
151
146
|
}
|
|
152
147
|
|
|
153
|
-
process.stdout.write(`agents freshness check passed (${targets.length} files)\n`);
|
|
148
|
+
process.stdout.write(`agents freshness check passed (${report.targets.length} files)\n`);
|
|
154
149
|
return 0;
|
|
155
150
|
}
|