@nimiplatform/nimi-coding 0.1.0
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/LICENSE +21 -0
- package/README.md +348 -0
- package/adapters/README.md +25 -0
- package/adapters/claude/README.md +89 -0
- package/adapters/claude/profile.yaml +70 -0
- package/adapters/codex/README.md +53 -0
- package/adapters/codex/profile.yaml +78 -0
- package/adapters/oh-my-codex/README.md +185 -0
- package/adapters/oh-my-codex/profile.yaml +46 -0
- package/bin/nimicoding.mjs +6 -0
- package/cli/commands/admit-high-risk-decision.mjs +108 -0
- package/cli/commands/audit-sweep.mjs +341 -0
- package/cli/commands/blueprint-audit.mjs +91 -0
- package/cli/commands/clear.mjs +168 -0
- package/cli/commands/closeout.mjs +183 -0
- package/cli/commands/decide-high-risk-execution.mjs +124 -0
- package/cli/commands/doctor.mjs +53 -0
- package/cli/commands/generate-spec-derived-docs.mjs +131 -0
- package/cli/commands/handoff.mjs +123 -0
- package/cli/commands/ingest-high-risk-execution.mjs +95 -0
- package/cli/commands/review-high-risk-execution.mjs +95 -0
- package/cli/commands/start.mjs +717 -0
- package/cli/commands/topic-formatters.mjs +382 -0
- package/cli/commands/topic-goal.mjs +33 -0
- package/cli/commands/topic-options-shared.mjs +27 -0
- package/cli/commands/topic-options-workflow.mjs +767 -0
- package/cli/commands/topic-options.mjs +626 -0
- package/cli/commands/topic-runner.mjs +169 -0
- package/cli/commands/topic.mjs +795 -0
- package/cli/commands/validate-acceptance.mjs +5 -0
- package/cli/commands/validate-ai-governance.mjs +214 -0
- package/cli/commands/validate-execution-packet.mjs +5 -0
- package/cli/commands/validate-orchestration-state.mjs +5 -0
- package/cli/commands/validate-prompt.mjs +5 -0
- package/cli/commands/validate-spec-audit.mjs +27 -0
- package/cli/commands/validate-spec-governance.mjs +124 -0
- package/cli/commands/validate-spec-tree.mjs +27 -0
- package/cli/commands/validate-worker-output.mjs +5 -0
- package/cli/constants.mjs +489 -0
- package/cli/help.mjs +134 -0
- package/cli/index.mjs +103 -0
- package/cli/lib/adapter-profiles.mjs +403 -0
- package/cli/lib/audit-execution.mjs +52 -0
- package/cli/lib/audit-sweep-runtime/admissions.mjs +381 -0
- package/cli/lib/audit-sweep-runtime/audit-validity.mjs +333 -0
- package/cli/lib/audit-sweep-runtime/chunks.mjs +697 -0
- package/cli/lib/audit-sweep-runtime/closeout.mjs +144 -0
- package/cli/lib/audit-sweep-runtime/codex-auditor-evidence.mjs +639 -0
- package/cli/lib/audit-sweep-runtime/codex-auditor.mjs +515 -0
- package/cli/lib/audit-sweep-runtime/common.mjs +329 -0
- package/cli/lib/audit-sweep-runtime/coverage-quality.mjs +172 -0
- package/cli/lib/audit-sweep-runtime/evidence-assignment.mjs +152 -0
- package/cli/lib/audit-sweep-runtime/format.mjs +57 -0
- package/cli/lib/audit-sweep-runtime/ingest.mjs +486 -0
- package/cli/lib/audit-sweep-runtime/inventory-spec-chunks.mjs +198 -0
- package/cli/lib/audit-sweep-runtime/inventory.mjs +728 -0
- package/cli/lib/audit-sweep-runtime/ledger.mjs +315 -0
- package/cli/lib/audit-sweep-runtime/p0p1-profile.mjs +101 -0
- package/cli/lib/audit-sweep-runtime/remediation.mjs +349 -0
- package/cli/lib/audit-sweep-runtime/rerun.mjs +129 -0
- package/cli/lib/audit-sweep-runtime/risk-budget.mjs +300 -0
- package/cli/lib/audit-sweep-runtime/status.mjs +62 -0
- package/cli/lib/audit-sweep-runtime/validators-ledger.mjs +215 -0
- package/cli/lib/audit-sweep-runtime/validators.mjs +758 -0
- package/cli/lib/audit-sweep.mjs +18 -0
- package/cli/lib/authority-convergence.mjs +309 -0
- package/cli/lib/blueprint-audit.mjs +370 -0
- package/cli/lib/bootstrap.mjs +228 -0
- package/cli/lib/closeout.mjs +623 -0
- package/cli/lib/codex-sdk-runner.mjs +76 -0
- package/cli/lib/contracts.mjs +180 -0
- package/cli/lib/doctor.mjs +18 -0
- package/cli/lib/entrypoints.mjs +274 -0
- package/cli/lib/external-execution.mjs +101 -0
- package/cli/lib/fs-helpers.mjs +33 -0
- package/cli/lib/handoff.mjs +785 -0
- package/cli/lib/high-risk-admission.mjs +442 -0
- package/cli/lib/high-risk-decision.mjs +324 -0
- package/cli/lib/high-risk-ingest.mjs +317 -0
- package/cli/lib/high-risk-review.mjs +263 -0
- package/cli/lib/internal/contracts-loaders.mjs +132 -0
- package/cli/lib/internal/contracts-parse-high-risk.mjs +131 -0
- package/cli/lib/internal/contracts-parse.mjs +457 -0
- package/cli/lib/internal/contracts-validators.mjs +398 -0
- package/cli/lib/internal/doctor-bootstrap-surface.mjs +359 -0
- package/cli/lib/internal/doctor-delegated-surface.mjs +256 -0
- package/cli/lib/internal/doctor-finalize.mjs +385 -0
- package/cli/lib/internal/doctor-format.mjs +286 -0
- package/cli/lib/internal/doctor-inspectors.mjs +294 -0
- package/cli/lib/internal/doctor-state.mjs +205 -0
- package/cli/lib/internal/governance/ai/ai-context-budget-core.mjs +315 -0
- package/cli/lib/internal/governance/ai/ai-structure-budget-core.mjs +358 -0
- package/cli/lib/internal/governance/ai/check-agents-freshness.mjs +155 -0
- package/cli/lib/internal/governance/ai/check-high-risk-doc-metadata-core.mjs +173 -0
- package/cli/lib/internal/governance/config.mjs +150 -0
- package/cli/lib/internal/governance/runner.mjs +35 -0
- package/cli/lib/internal/governance/shared/read-yaml-with-fragments.mjs +49 -0
- package/cli/lib/internal/validators-artifacts.mjs +515 -0
- package/cli/lib/internal/validators-shared.mjs +28 -0
- package/cli/lib/internal/validators-spec-helpers.mjs +186 -0
- package/cli/lib/internal/validators-spec.mjs +410 -0
- package/cli/lib/shared.mjs +83 -0
- package/cli/lib/topic-draft-packets.mjs +48 -0
- package/cli/lib/topic-goal.mjs +361 -0
- package/cli/lib/topic-runner.mjs +772 -0
- package/cli/lib/topic.mjs +93 -0
- package/cli/lib/ui.mjs +178 -0
- package/cli/lib/validators.mjs +78 -0
- package/cli/lib/value-helpers.mjs +24 -0
- package/cli/lib/yaml-helpers.mjs +133 -0
- package/cli/nimicoding.mjs +1 -0
- package/cli/seeds/bootstrap.mjs +47 -0
- package/config/audit-execution-artifacts.yaml +20 -0
- package/config/bootstrap.yaml +6 -0
- package/config/external-execution-artifacts.yaml +16 -0
- package/config/host-adapter.yaml +30 -0
- package/config/host-profile.yaml +29 -0
- package/config/installer-evidence.yaml +31 -0
- package/config/skill-installer.yaml +23 -0
- package/config/skill-manifest.yaml +46 -0
- package/config/skills.yaml +30 -0
- package/config/spec-generation-inputs.yaml +25 -0
- package/contracts/acceptance.schema.yaml +16 -0
- package/contracts/admission-checklist.schema.yaml +15 -0
- package/contracts/audit-chunk.schema.yaml +110 -0
- package/contracts/audit-closeout.schema.yaml +51 -0
- package/contracts/audit-finding.schema.yaml +61 -0
- package/contracts/audit-ledger.schema.yaml +138 -0
- package/contracts/audit-plan.schema.yaml +123 -0
- package/contracts/audit-remediation-map.schema.yaml +51 -0
- package/contracts/audit-rerun.schema.yaml +31 -0
- package/contracts/audit-sweep-result.yaml +49 -0
- package/contracts/authority-convergence-audit.schema.yaml +19 -0
- package/contracts/closeout.schema.yaml +25 -0
- package/contracts/decision-review.schema.yaml +16 -0
- package/contracts/doc-spec-audit-result.yaml +19 -0
- package/contracts/execution-packet.schema.yaml +49 -0
- package/contracts/external-host-compatibility.yaml +22 -0
- package/contracts/forbidden-shortcuts.catalog.yaml +23 -0
- package/contracts/high-risk-admission.schema.yaml +23 -0
- package/contracts/high-risk-execution-result.yaml +20 -0
- package/contracts/orchestration-state.schema.yaml +41 -0
- package/contracts/overflow-continuation.schema.yaml +12 -0
- package/contracts/packet.schema.yaml +30 -0
- package/contracts/pending-note.schema.yaml +17 -0
- package/contracts/prompt.schema.yaml +12 -0
- package/contracts/remediation.schema.yaml +16 -0
- package/contracts/result.schema.yaml +24 -0
- package/contracts/spec-generation-audit.schema.yaml +31 -0
- package/contracts/spec-generation-inputs.schema.yaml +39 -0
- package/contracts/spec-reconstruction-result.yaml +37 -0
- package/contracts/topic-goal.schema.yaml +78 -0
- package/contracts/topic-run-ledger.schema.yaml +72 -0
- package/contracts/topic-step-decision.schema.yaml +45 -0
- package/contracts/topic.schema.yaml +65 -0
- package/contracts/true-close.schema.yaml +15 -0
- package/contracts/wave.schema.yaml +29 -0
- package/contracts/worker-output.schema.yaml +15 -0
- package/methodology/audit-sweep-p0p1-recall.yaml +45 -0
- package/methodology/authority-convergence-policy.yaml +42 -0
- package/methodology/core.yaml +25 -0
- package/methodology/four-closure-policy.yaml +28 -0
- package/methodology/overflow-continuation-policy.yaml +14 -0
- package/methodology/role-separation-policy.yaml +28 -0
- package/methodology/skill-exchange-projection.yaml +114 -0
- package/methodology/skill-handoff.yaml +34 -0
- package/methodology/skill-installer-result.yaml +27 -0
- package/methodology/skill-installer-summary-projection.yaml +181 -0
- package/methodology/skill-runtime.yaml +23 -0
- package/methodology/spec-reconstruction.yaml +63 -0
- package/methodology/spec-target-truth-profile.yaml +53 -0
- package/methodology/topic-lifecycle-report.yaml +144 -0
- package/methodology/topic-lifecycle.yaml +37 -0
- package/methodology/topic-naming-ontology.yaml +21 -0
- package/methodology/topic-ontology.yaml +38 -0
- package/methodology/topic-validation-policy.yaml +9 -0
- package/methodology/wave-dag-policy.yaml +14 -0
- package/package.json +50 -0
- package/spec/_meta/command-gating-matrix.yaml +110 -0
- package/spec/_meta/generate-drift-migration-checklist.yaml +155 -0
- package/spec/_meta/governance-routing-cutover-checklist.yaml +35 -0
- package/spec/_meta/phase2-impacted-surface-matrix.yaml +44 -0
- package/spec/_meta/spec-authority-cutover-readiness.yaml +104 -0
- package/spec/_meta/spec-tree-model.yaml +72 -0
- package/spec/bootstrap-state.yaml +99 -0
- package/spec/product-scope.yaml +56 -0
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
loadBlueprintReference as loadBlueprintReferenceInternal,
|
|
5
|
+
loadCommandGatingMatrix as loadCommandGatingMatrixInternal,
|
|
6
|
+
loadAuditSweepContract as loadAuditSweepContractInternal,
|
|
7
|
+
loadDocSpecAuditContract as loadDocSpecAuditContractInternal,
|
|
8
|
+
loadExternalHostCompatibilityContract as loadExternalHostCompatibilityContractInternal,
|
|
9
|
+
loadHighRiskAdmissionContract as loadHighRiskAdmissionContractInternal,
|
|
10
|
+
loadHighRiskExecutionContract as loadHighRiskExecutionContractInternal,
|
|
11
|
+
loadHighRiskSchemaContracts as loadHighRiskSchemaContractsInternal,
|
|
12
|
+
loadSpecGenerationAuditContract as loadSpecGenerationAuditContractInternal,
|
|
13
|
+
loadSpecGenerationInputsConfig as loadSpecGenerationInputsConfigInternal,
|
|
14
|
+
loadSpecGenerationInputsContract as loadSpecGenerationInputsContractInternal,
|
|
15
|
+
loadSpecReconstructionContract as loadSpecReconstructionContractInternal,
|
|
16
|
+
loadSpecTreeModelContract as loadSpecTreeModelContractInternal,
|
|
17
|
+
} from "./internal/contracts-loaders.mjs";
|
|
18
|
+
import { readTextIfFile } from "./fs-helpers.mjs";
|
|
19
|
+
import { parseYamlText } from "./yaml-helpers.mjs";
|
|
20
|
+
import { matchCommandGatingRule } from "./internal/contracts-parse.mjs";
|
|
21
|
+
import {
|
|
22
|
+
validateAuditSweepSummary as validateAuditSweepSummaryInternal,
|
|
23
|
+
validateDocSpecAuditSummary as validateDocSpecAuditSummaryInternal,
|
|
24
|
+
validateHighRiskAdmissionRecord as validateHighRiskAdmissionRecordInternal,
|
|
25
|
+
validateHighRiskAdmissionsSpec as validateHighRiskAdmissionsSpecInternal,
|
|
26
|
+
validateHighRiskExecutionSummary as validateHighRiskExecutionSummaryInternal,
|
|
27
|
+
validateSpecReconstructionSummary as validateSpecReconstructionSummaryInternal,
|
|
28
|
+
} from "./internal/contracts-validators.mjs";
|
|
29
|
+
|
|
30
|
+
export function findCommandGatingRule(commandGatingMatrix, command, skillId = null) {
|
|
31
|
+
return matchCommandGatingRule(commandGatingMatrix, command, skillId);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function loadSpecReconstructionContract(projectRoot) {
|
|
35
|
+
return loadSpecReconstructionContractInternal(projectRoot);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function loadSpecTreeModelContract(projectRoot) {
|
|
39
|
+
return loadSpecTreeModelContractInternal(projectRoot);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function loadSpecGenerationInputsContract(projectRoot) {
|
|
43
|
+
return loadSpecGenerationInputsContractInternal(projectRoot);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function loadSpecGenerationAuditContract(projectRoot) {
|
|
47
|
+
return loadSpecGenerationAuditContractInternal(projectRoot);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function loadSpecGenerationInputsConfig(projectRoot) {
|
|
51
|
+
return loadSpecGenerationInputsConfigInternal(projectRoot);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function loadCommandGatingMatrix(projectRoot) {
|
|
55
|
+
return loadCommandGatingMatrixInternal(projectRoot);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function loadBlueprintReference(projectRoot) {
|
|
59
|
+
return loadBlueprintReferenceInternal(projectRoot);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function loadDocSpecAuditContract(projectRoot) {
|
|
63
|
+
return loadDocSpecAuditContractInternal(projectRoot);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function loadAuditSweepContract(projectRoot) {
|
|
67
|
+
return loadAuditSweepContractInternal(projectRoot);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function loadHighRiskExecutionContract(projectRoot) {
|
|
71
|
+
return loadHighRiskExecutionContractInternal(projectRoot);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function loadHighRiskAdmissionContract(projectRoot) {
|
|
75
|
+
return loadHighRiskAdmissionContractInternal(projectRoot);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function loadExternalHostCompatibilityContract(projectRoot) {
|
|
79
|
+
return loadExternalHostCompatibilityContractInternal(projectRoot);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function loadHighRiskSchemaContracts(projectRoot) {
|
|
83
|
+
return loadHighRiskSchemaContractsInternal(projectRoot);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function validateSpecReconstructionSummary(summary, contract, verifiedAt) {
|
|
87
|
+
return validateSpecReconstructionSummaryInternal(summary, contract, verifiedAt);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function validateDocSpecAuditSummary(summary, contract, verifiedAt) {
|
|
91
|
+
return validateDocSpecAuditSummaryInternal(summary, contract, verifiedAt);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function validateAuditSweepSummary(summary, contract, verifiedAt) {
|
|
95
|
+
return validateAuditSweepSummaryInternal(summary, contract, verifiedAt);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function validateHighRiskExecutionSummary(summary, contract, verifiedAt) {
|
|
99
|
+
return validateHighRiskExecutionSummaryInternal(summary, contract, verifiedAt);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function validateHighRiskAdmissionRecord(record, contract) {
|
|
103
|
+
return validateHighRiskAdmissionRecordInternal(record, contract);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function validateHighRiskAdmissionsSpec(spec, contract) {
|
|
107
|
+
return validateHighRiskAdmissionsSpecInternal(spec, contract);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async function loadYamlWithFallback(projectRoot, primaryRef, fallbackRef) {
|
|
111
|
+
const primaryText = await readTextIfFile(path.join(projectRoot, primaryRef));
|
|
112
|
+
if (primaryText !== null) {
|
|
113
|
+
return {
|
|
114
|
+
path: primaryRef,
|
|
115
|
+
text: primaryText,
|
|
116
|
+
data: parseYamlText(primaryText),
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const fallbackText = await readTextIfFile(path.join(projectRoot, fallbackRef));
|
|
121
|
+
return {
|
|
122
|
+
path: fallbackRef,
|
|
123
|
+
text: fallbackText,
|
|
124
|
+
data: parseYamlText(fallbackText),
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export async function loadTopicRuntimeContracts(projectRoot) {
|
|
129
|
+
const [
|
|
130
|
+
topicSchema,
|
|
131
|
+
waveSchema,
|
|
132
|
+
packetSchema,
|
|
133
|
+
resultSchema,
|
|
134
|
+
closeoutSchema,
|
|
135
|
+
remediationSchema,
|
|
136
|
+
decisionReviewSchema,
|
|
137
|
+
pendingNoteSchema,
|
|
138
|
+
topicStepDecisionSchema,
|
|
139
|
+
topicRunLedgerSchema,
|
|
140
|
+
forbiddenShortcutsCatalog,
|
|
141
|
+
lifecycleReport,
|
|
142
|
+
fourClosurePolicy,
|
|
143
|
+
validationPolicy,
|
|
144
|
+
authorityConvergencePolicy,
|
|
145
|
+
] = 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"),
|
|
161
|
+
]);
|
|
162
|
+
|
|
163
|
+
return {
|
|
164
|
+
topicSchema,
|
|
165
|
+
waveSchema,
|
|
166
|
+
packetSchema,
|
|
167
|
+
resultSchema,
|
|
168
|
+
closeoutSchema,
|
|
169
|
+
remediationSchema,
|
|
170
|
+
decisionReviewSchema,
|
|
171
|
+
pendingNoteSchema,
|
|
172
|
+
topicStepDecisionSchema,
|
|
173
|
+
topicRunLedgerSchema,
|
|
174
|
+
forbiddenShortcutsCatalog,
|
|
175
|
+
lifecycleReport,
|
|
176
|
+
fourClosurePolicy,
|
|
177
|
+
validationPolicy,
|
|
178
|
+
authorityConvergencePolicy,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { inspectDoctorBootstrapSurface } from "./internal/doctor-bootstrap-surface.mjs";
|
|
2
|
+
import { inspectDoctorDelegatedSurface } from "./internal/doctor-delegated-surface.mjs";
|
|
3
|
+
import { finalizeDoctorState } from "./internal/doctor-finalize.mjs";
|
|
4
|
+
import { formatDoctorResult as formatDoctorResultInternal } from "./internal/doctor-format.mjs";
|
|
5
|
+
|
|
6
|
+
export async function inspectDoctorState(projectRoot) {
|
|
7
|
+
const bootstrapSurface = await inspectDoctorBootstrapSurface(projectRoot);
|
|
8
|
+
if (bootstrapSurface.done) {
|
|
9
|
+
return bootstrapSurface.result;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const delegatedSurface = await inspectDoctorDelegatedSurface(projectRoot, bootstrapSurface);
|
|
13
|
+
return finalizeDoctorState(projectRoot, bootstrapSurface, delegatedSurface);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function formatDoctorResult(result, options = {}) {
|
|
17
|
+
return formatDoctorResultInternal(result, options);
|
|
18
|
+
}
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
import { readFile, rm, writeFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
AGENTS_BEGIN,
|
|
6
|
+
AGENTS_END,
|
|
7
|
+
CLAUDE_BEGIN,
|
|
8
|
+
CLAUDE_END,
|
|
9
|
+
} from "../constants.mjs";
|
|
10
|
+
import { pathExists } from "./fs-helpers.mjs";
|
|
11
|
+
|
|
12
|
+
function managedAgentsBlock() {
|
|
13
|
+
return `${AGENTS_BEGIN}
|
|
14
|
+
# Nimi Coding Managed Block
|
|
15
|
+
|
|
16
|
+
- Read .nimi/methodology, .nimi/spec, and .nimi/contracts before high-risk changes.
|
|
17
|
+
- Treat .nimi as the primary AI truth surface for this project.
|
|
18
|
+
- Treat \`/.nimi/spec/**\` as the current repo-wide product authority for this project, and use Git history for retired pre-cutover authority evidence.
|
|
19
|
+
- If .nimi/spec remains bootstrap-only, use .nimi/methodology/spec-reconstruction.yaml and .nimi/config/skills.yaml to drive AI-side truth reconstruction.
|
|
20
|
+
- Treat .nimi/methodology/spec-target-truth-profile.yaml as repo-local support guidance for future governance slices, not as the canonical reconstruction completion target or a guaranteed fresh-bootstrap seed.
|
|
21
|
+
- Treat .nimi/contracts/spec-reconstruction-result.yaml, .nimi/contracts/doc-spec-audit-result.yaml, .nimi/contracts/high-risk-execution-result.yaml, and .nimi/contracts/high-risk-admission.schema.yaml as machine contracts for reconstruction, audit, local-only high-risk closeout summaries, and canonical high-risk admission truth.
|
|
22
|
+
- Treat .nimi/config/skill-manifest.yaml, .nimi/config/host-profile.yaml, .nimi/config/host-adapter.yaml, .nimi/config/external-execution-artifacts.yaml, .nimi/config/skill-installer.yaml, .nimi/methodology/skill-runtime.yaml, .nimi/methodology/skill-installer-result.yaml, .nimi/methodology/skill-handoff.yaml, and admitted package-owned adapter profiles under adapters/**/profile.yaml as the canonical bridge to any external AI/skill execution.
|
|
23
|
+
- Treat standalone nimicoding as boundary-complete for bootstrap, handoff, validation, projection, and explicit admission only; do not assume packaged run-kernel, provider, scheduler, notification, or automation ownership.
|
|
24
|
+
- Treat .nimi/config/installer-evidence.yaml and .nimi/methodology/skill-installer-summary-projection.yaml as the operational-to-semantic installer projection boundary; do not promote concrete evidence artifacts into semantic truth.
|
|
25
|
+
- Treat high-risk external execution closeout, decision, ingest, and review payloads under .nimi/local/** as local-only operational projections; they do not promote semantic truth automatically, even when manager-owned.
|
|
26
|
+
- Use high-risk packetized execution only when authority, ownership, or cross-layer risk justifies it.
|
|
27
|
+
- Keep inline manager-worker as the default methodology posture; do not assume a separate worker runtime is mandatory.
|
|
28
|
+
- Keep code changes AI-context-efficient: favor bounded, cohesive files and split by responsibility during implementation instead of first concentrating unrelated logic into one file.
|
|
29
|
+
- Keep the methodology continuity-agnostic; do not assume daemon, heartbeat, or persistent manager ownership.
|
|
30
|
+
- Treat cutover readiness as preflight evidence only; the authority flip must come from an admitted cutover batch, not from readiness green by itself.
|
|
31
|
+
- Do not treat this managed block as a replacement for project-specific rules outside .nimi.
|
|
32
|
+
${AGENTS_END}`;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function managedClaudeBlock() {
|
|
36
|
+
return `${CLAUDE_BEGIN}
|
|
37
|
+
# Nimi Coding Managed Block
|
|
38
|
+
|
|
39
|
+
Use the project's .nimi layer as the primary AI truth surface.
|
|
40
|
+
|
|
41
|
+
Priority:
|
|
42
|
+
1. .nimi/methodology
|
|
43
|
+
2. .nimi/spec
|
|
44
|
+
3. .nimi/contracts
|
|
45
|
+
4. .nimi/config
|
|
46
|
+
5. repository-local AI entrypoint files
|
|
47
|
+
|
|
48
|
+
If the project still exposes only bootstrap seed files, use the reconstruction guidance, result contracts, manifest, host-profile, host-adapter, admitted package-owned adapter profiles, installer, runtime contract, installer result contract, collapsed installer summary projection lifecycle contract, operational evidence guidance, and handoff truth under .nimi rather than assuming skills are already installed.
|
|
49
|
+
|
|
50
|
+
Default posture:
|
|
51
|
+
- use risk-shaped methodology only for authority-bearing or high-risk work
|
|
52
|
+
- prefer inline manager-worker unless a later admitted packet expands runtime ownership
|
|
53
|
+
- keep code changes AI-context-efficient: prefer bounded cohesive files and split by responsibility during implementation instead of first concentrating unrelated logic into one file
|
|
54
|
+
- keep continuity-agnostic semantics; do not assume persistent automation or self-hosting
|
|
55
|
+
- treat handoff --json as the authoritative machine contract and handoff --prompt as a human-readable projection only
|
|
56
|
+
- treat \`/.nimi/spec/**\` as today's repo-wide authority, treat pre-cutover authority history as Git-only, and treat cutover readiness as historical preflight evidence rather than the authority source
|
|
57
|
+
${CLAUDE_END}`;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function upsertManagedBlock(existing, begin, end, block) {
|
|
61
|
+
const pattern = new RegExp(
|
|
62
|
+
`${begin.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}[\\s\\S]*?${end.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}`,
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
if (pattern.test(existing)) {
|
|
66
|
+
return existing.replace(pattern, block);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const prefix = existing.length === 0 ? "" : existing.endsWith("\n") ? "\n" : "\n\n";
|
|
70
|
+
return `${existing}${prefix}${block}\n`;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function removeManagedBlock(existing, begin, end) {
|
|
74
|
+
const pattern = new RegExp(
|
|
75
|
+
`(?:\\n\\n)?${begin.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}[\\s\\S]*?${end.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?:\\n)?`,
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
if (!pattern.test(existing)) {
|
|
79
|
+
return {
|
|
80
|
+
changed: false,
|
|
81
|
+
next: existing,
|
|
82
|
+
deleteFile: false,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const next = existing
|
|
87
|
+
.replace(pattern, "\n")
|
|
88
|
+
.replace(/\n{3,}/g, "\n\n")
|
|
89
|
+
.replace(/^\n+/, "")
|
|
90
|
+
.trimEnd();
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
changed: true,
|
|
94
|
+
next: next.length === 0 ? "" : `${next}\n`,
|
|
95
|
+
deleteFile: next.length === 0,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function computeTextFileUpdate(existing, block, begin, end, header) {
|
|
100
|
+
const base = existing ?? header;
|
|
101
|
+
const next = upsertManagedBlock(base, begin, end, block);
|
|
102
|
+
return {
|
|
103
|
+
changed: next !== base,
|
|
104
|
+
next,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async function upsertTextFile(filePath, block, begin, end, header) {
|
|
109
|
+
const existing = (await pathExists(filePath)) ? await readFile(filePath, "utf8") : header;
|
|
110
|
+
const computed = computeTextFileUpdate(existing, block, begin, end, header);
|
|
111
|
+
|
|
112
|
+
if (!computed.changed) {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
await writeFile(filePath, computed.next, "utf8");
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async function removeManagedTextFile(filePath, begin, end, emptyFallbackHeader = null) {
|
|
121
|
+
const existing = (await pathExists(filePath)) ? await readFile(filePath, "utf8") : null;
|
|
122
|
+
if (existing === null) {
|
|
123
|
+
return {
|
|
124
|
+
changed: false,
|
|
125
|
+
removedFile: false,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const computed = removeManagedBlock(existing, begin, end);
|
|
130
|
+
if (!computed.changed) {
|
|
131
|
+
return {
|
|
132
|
+
changed: false,
|
|
133
|
+
removedFile: false,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (computed.deleteFile || (emptyFallbackHeader && computed.next.trim() === emptyFallbackHeader.trim())) {
|
|
138
|
+
await rm(filePath, { force: true });
|
|
139
|
+
return {
|
|
140
|
+
changed: true,
|
|
141
|
+
removedFile: true,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
await writeFile(filePath, computed.next, "utf8");
|
|
146
|
+
return {
|
|
147
|
+
changed: true,
|
|
148
|
+
removedFile: false,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export async function previewEntrypointIntegration(projectRoot) {
|
|
153
|
+
const updates = [];
|
|
154
|
+
const files = [
|
|
155
|
+
{
|
|
156
|
+
path: path.join(projectRoot, "AGENTS.md"),
|
|
157
|
+
block: managedAgentsBlock(),
|
|
158
|
+
begin: AGENTS_BEGIN,
|
|
159
|
+
end: AGENTS_END,
|
|
160
|
+
header: "# AGENTS.md\n",
|
|
161
|
+
relativePath: "AGENTS.md",
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
path: path.join(projectRoot, "CLAUDE.md"),
|
|
165
|
+
block: managedClaudeBlock(),
|
|
166
|
+
begin: CLAUDE_BEGIN,
|
|
167
|
+
end: CLAUDE_END,
|
|
168
|
+
header: "# CLAUDE.md\n",
|
|
169
|
+
relativePath: "CLAUDE.md",
|
|
170
|
+
},
|
|
171
|
+
];
|
|
172
|
+
|
|
173
|
+
for (const file of files) {
|
|
174
|
+
const existing = (await pathExists(file.path)) ? await readFile(file.path, "utf8") : null;
|
|
175
|
+
const computed = computeTextFileUpdate(existing, file.block, file.begin, file.end, file.header);
|
|
176
|
+
if (computed.changed) {
|
|
177
|
+
updates.push(file.relativePath);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return updates;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export async function integrateEntrypoints(projectRoot) {
|
|
185
|
+
const updated = [];
|
|
186
|
+
|
|
187
|
+
if (
|
|
188
|
+
await upsertTextFile(
|
|
189
|
+
path.join(projectRoot, "AGENTS.md"),
|
|
190
|
+
managedAgentsBlock(),
|
|
191
|
+
AGENTS_BEGIN,
|
|
192
|
+
AGENTS_END,
|
|
193
|
+
"# AGENTS.md\n",
|
|
194
|
+
)
|
|
195
|
+
) {
|
|
196
|
+
updated.push("AGENTS.md");
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (
|
|
200
|
+
await upsertTextFile(
|
|
201
|
+
path.join(projectRoot, "CLAUDE.md"),
|
|
202
|
+
managedClaudeBlock(),
|
|
203
|
+
CLAUDE_BEGIN,
|
|
204
|
+
CLAUDE_END,
|
|
205
|
+
"# CLAUDE.md\n",
|
|
206
|
+
)
|
|
207
|
+
) {
|
|
208
|
+
updated.push("CLAUDE.md");
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return updated;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export async function previewEntrypointRemoval(projectRoot) {
|
|
215
|
+
const updates = [];
|
|
216
|
+
const files = [
|
|
217
|
+
{
|
|
218
|
+
path: path.join(projectRoot, "AGENTS.md"),
|
|
219
|
+
begin: AGENTS_BEGIN,
|
|
220
|
+
end: AGENTS_END,
|
|
221
|
+
relativePath: "AGENTS.md",
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
path: path.join(projectRoot, "CLAUDE.md"),
|
|
225
|
+
begin: CLAUDE_BEGIN,
|
|
226
|
+
end: CLAUDE_END,
|
|
227
|
+
relativePath: "CLAUDE.md",
|
|
228
|
+
},
|
|
229
|
+
];
|
|
230
|
+
|
|
231
|
+
for (const file of files) {
|
|
232
|
+
const existing = (await pathExists(file.path)) ? await readFile(file.path, "utf8") : null;
|
|
233
|
+
if (existing === null) {
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const computed = removeManagedBlock(existing, file.begin, file.end);
|
|
238
|
+
if (computed.changed) {
|
|
239
|
+
updates.push(file.relativePath);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return updates;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
export async function removeManagedEntrypoints(projectRoot) {
|
|
247
|
+
const updatedFiles = [];
|
|
248
|
+
const removedFiles = [];
|
|
249
|
+
|
|
250
|
+
const agentsResult = await removeManagedTextFile(
|
|
251
|
+
path.join(projectRoot, "AGENTS.md"),
|
|
252
|
+
AGENTS_BEGIN,
|
|
253
|
+
AGENTS_END,
|
|
254
|
+
"# AGENTS.md",
|
|
255
|
+
);
|
|
256
|
+
if (agentsResult.changed) {
|
|
257
|
+
(agentsResult.removedFile ? removedFiles : updatedFiles).push("AGENTS.md");
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const claudeResult = await removeManagedTextFile(
|
|
261
|
+
path.join(projectRoot, "CLAUDE.md"),
|
|
262
|
+
CLAUDE_BEGIN,
|
|
263
|
+
CLAUDE_END,
|
|
264
|
+
"# CLAUDE.md",
|
|
265
|
+
);
|
|
266
|
+
if (claudeResult.changed) {
|
|
267
|
+
(claudeResult.removedFile ? removedFiles : updatedFiles).push("CLAUDE.md");
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
return {
|
|
271
|
+
updatedFiles,
|
|
272
|
+
removedFiles,
|
|
273
|
+
};
|
|
274
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
EXTERNAL_EXECUTION_ARTIFACTS_CONFIG_REF,
|
|
5
|
+
HIGH_RISK_EXECUTION_ARTIFACT_HARD_CONSTRAINTS,
|
|
6
|
+
HIGH_RISK_EXECUTION_ARTIFACT_ROOTS,
|
|
7
|
+
HIGH_RISK_EXECUTION_RESULT_CONTRACT_REF,
|
|
8
|
+
HOST_ADAPTER_CONFIG_REF,
|
|
9
|
+
} from "../constants.mjs";
|
|
10
|
+
import { readTextIfFile } from "./fs-helpers.mjs";
|
|
11
|
+
import { arraysEqual, isPlainObject, toStringArray } from "./value-helpers.mjs";
|
|
12
|
+
import { parseYamlText } from "./yaml-helpers.mjs";
|
|
13
|
+
|
|
14
|
+
function parseExternalExecutionArtifactsConfig(text) {
|
|
15
|
+
const parsed = parseYamlText(text);
|
|
16
|
+
const root = parsed?.external_execution_artifacts;
|
|
17
|
+
const artifactRoots = isPlainObject(root?.artifact_roots) ? root.artifact_roots : null;
|
|
18
|
+
const hardConstraints = toStringArray(root?.hard_constraints);
|
|
19
|
+
|
|
20
|
+
const artifactRootsOk = Boolean(artifactRoots)
|
|
21
|
+
&& Object.entries(HIGH_RISK_EXECUTION_ARTIFACT_ROOTS).every(
|
|
22
|
+
([field, expectedPath]) => String(artifactRoots[field] ?? "") === expectedPath,
|
|
23
|
+
)
|
|
24
|
+
&& Object.keys(artifactRoots).length === Object.keys(HIGH_RISK_EXECUTION_ARTIFACT_ROOTS).length;
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
ok: String(root?.skill_id ?? "") === "high_risk_execution"
|
|
28
|
+
&& String(root?.host_adapter_ref ?? "") === HOST_ADAPTER_CONFIG_REF
|
|
29
|
+
&& String(root?.result_contract_ref ?? "") === HIGH_RISK_EXECUTION_RESULT_CONTRACT_REF
|
|
30
|
+
&& String(root?.locality ?? "") === "local_only"
|
|
31
|
+
&& artifactRootsOk
|
|
32
|
+
&& arraysEqual(hardConstraints, HIGH_RISK_EXECUTION_ARTIFACT_HARD_CONSTRAINTS),
|
|
33
|
+
artifactRoots: artifactRootsOk
|
|
34
|
+
? Object.fromEntries(
|
|
35
|
+
Object.entries(HIGH_RISK_EXECUTION_ARTIFACT_ROOTS).map(([field]) => [field, artifactRoots[field]]),
|
|
36
|
+
)
|
|
37
|
+
: null,
|
|
38
|
+
hardConstraints,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function normalizeRef(ref) {
|
|
43
|
+
return path.posix.normalize(String(ref ?? "")).replace(/^\.\/+/, "");
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function isRefUnderRoot(ref, root) {
|
|
47
|
+
const normalizedRef = normalizeRef(ref);
|
|
48
|
+
const normalizedRoot = normalizeRef(root);
|
|
49
|
+
|
|
50
|
+
return normalizedRef === normalizedRoot || normalizedRef.startsWith(`${normalizedRoot}/`);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export async function loadExternalExecutionArtifactsConfig(projectRoot) {
|
|
54
|
+
const text = await readTextIfFile(
|
|
55
|
+
path.join(projectRoot, EXTERNAL_EXECUTION_ARTIFACTS_CONFIG_REF),
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
path: EXTERNAL_EXECUTION_ARTIFACTS_CONFIG_REF,
|
|
60
|
+
text,
|
|
61
|
+
...parseExternalExecutionArtifactsConfig(text),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function validateHighRiskExecutionArtifactRefs(summary, config) {
|
|
66
|
+
if (!config.ok || !config.artifactRoots) {
|
|
67
|
+
return {
|
|
68
|
+
ok: false,
|
|
69
|
+
reason: "external execution artifacts config is missing or malformed",
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
for (const field of [
|
|
74
|
+
"packet_ref",
|
|
75
|
+
"orchestration_state_ref",
|
|
76
|
+
"prompt_ref",
|
|
77
|
+
"worker_output_ref",
|
|
78
|
+
]) {
|
|
79
|
+
const expectedRoot = config.artifactRoots[field];
|
|
80
|
+
if (!isRefUnderRoot(summary[field], expectedRoot)) {
|
|
81
|
+
return {
|
|
82
|
+
ok: false,
|
|
83
|
+
reason: `high_risk_execution summary.${field} must stay under ${expectedRoot}`,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const evidenceRoot = config.artifactRoots.evidence_refs;
|
|
89
|
+
for (const ref of summary.evidence_refs) {
|
|
90
|
+
if (!isRefUnderRoot(ref, evidenceRoot)) {
|
|
91
|
+
return {
|
|
92
|
+
ok: false,
|
|
93
|
+
reason: `high_risk_execution summary.evidence_refs entries must stay under ${evidenceRoot}`,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
ok: true,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { readFile, stat, writeFile } from "node:fs/promises";
|
|
2
|
+
|
|
3
|
+
export async function pathExists(targetPath) {
|
|
4
|
+
try {
|
|
5
|
+
return await stat(targetPath);
|
|
6
|
+
} catch {
|
|
7
|
+
return null;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export async function readTextIfFile(filePath) {
|
|
12
|
+
const info = await pathExists(filePath);
|
|
13
|
+
if (!info || !info.isFile()) {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return readFile(filePath, "utf8");
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function appendGitignoreEntries(gitignorePath, entries) {
|
|
21
|
+
const existing = (await pathExists(gitignorePath))
|
|
22
|
+
? await readFile(gitignorePath, "utf8")
|
|
23
|
+
: "";
|
|
24
|
+
const missing = entries.filter((entry) => !existing.includes(entry));
|
|
25
|
+
|
|
26
|
+
if (missing.length === 0) {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const prefix = existing.length === 0 || existing.endsWith("\n") ? "" : "\n";
|
|
31
|
+
await writeFile(gitignorePath, `${existing}${prefix}${missing.join("\n")}\n`, "utf8");
|
|
32
|
+
return true;
|
|
33
|
+
}
|