@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,785 @@
|
|
|
1
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import {
|
|
4
|
+
DOC_SPEC_AUDIT_RESULT_CONTRACT_REF,
|
|
5
|
+
EXTERNAL_HOST_COMPATIBILITY_CONTRACT_REF,
|
|
6
|
+
EXTERNAL_EXECUTION_ARTIFACTS_CONFIG_REF,
|
|
7
|
+
HANDOFF_PAYLOAD_CONTRACT_VERSION,
|
|
8
|
+
HOST_ADAPTER_CONFIG_REF,
|
|
9
|
+
SKILL_RESULT_CONTRACT_REFS,
|
|
10
|
+
SPEC_GENERATION_AUDIT_CONTRACT_REF,
|
|
11
|
+
SPEC_GENERATION_AUDIT_REF,
|
|
12
|
+
SPEC_RECONSTRUCTION_RESULT_CONTRACT_REF,
|
|
13
|
+
} from "../constants.mjs";
|
|
14
|
+
import {
|
|
15
|
+
loadAuditSweepContract,
|
|
16
|
+
loadDocSpecAuditContract,
|
|
17
|
+
loadExternalHostCompatibilityContract,
|
|
18
|
+
loadHighRiskSchemaContracts,
|
|
19
|
+
loadSpecGenerationInputsConfig,
|
|
20
|
+
loadSpecReconstructionContract,
|
|
21
|
+
loadSpecTreeModelContract,
|
|
22
|
+
} from "./contracts.mjs";
|
|
23
|
+
import { loadAuditExecutionArtifactsConfig } from "./audit-execution.mjs";
|
|
24
|
+
import { inspectDoctorState } from "./doctor.mjs";
|
|
25
|
+
import { loadExternalExecutionArtifactsConfig } from "./external-execution.mjs";
|
|
26
|
+
import { readTextIfFile } from "./fs-helpers.mjs";
|
|
27
|
+
import {
|
|
28
|
+
localize,
|
|
29
|
+
styleHeading,
|
|
30
|
+
styleLabel,
|
|
31
|
+
styleStatus,
|
|
32
|
+
} from "./ui.mjs";
|
|
33
|
+
import { mergeOrderedPaths, parseSkillSection, parseYamlText, readYamlList } from "./yaml-helpers.mjs";
|
|
34
|
+
function translateHandoffReason(reason) {
|
|
35
|
+
const translations = new Map([
|
|
36
|
+
["Bootstrap or handoff validation is failing; repair doctor errors before exporting handoff payloads", "bootstrap 或 handoff 校验失败;请先修复 doctor 报错,再导出 handoff payload"],
|
|
37
|
+
["Projects may delegate spec reconstruction to an external AI host when canonical tree work is needed", "当需要 canonical tree 工作时,项目可以将 spec reconstruction 委托给外部 AI host"],
|
|
38
|
+
["This skill is not allowed in the current lifecycle state", "当前生命周期状态不允许执行该 skill"],
|
|
39
|
+
["This skill is not allowed in the current authority mode", "当前 authority mode 不允许执行该 skill"],
|
|
40
|
+
["Skill prerequisites are satisfied by the current project-local truth", "当前项目本地 truth 已满足该 skill 的前置条件"],
|
|
41
|
+
]);
|
|
42
|
+
const delegatePrefix = "Delegate explicit skill execution for `";
|
|
43
|
+
if (reason.startsWith(delegatePrefix) && reason.endsWith("` to external_ai_host.")) {
|
|
44
|
+
const skillId = reason.slice(delegatePrefix.length, reason.length - "` to external_ai_host.".length);
|
|
45
|
+
return `将 \`${skillId}\` 的显式 skill 执行委托给 external_ai_host。`;
|
|
46
|
+
}
|
|
47
|
+
return translations.get(reason) ?? reason;
|
|
48
|
+
}
|
|
49
|
+
function commandRuleAllowsCurrentState(rule, doctorResult) {
|
|
50
|
+
if (!rule) {
|
|
51
|
+
return {
|
|
52
|
+
ok: false,
|
|
53
|
+
reason: "This skill is not allowed in the current lifecycle state",
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
const treeState = doctorResult.lifecycleState?.treeState;
|
|
57
|
+
const authorityMode = doctorResult.lifecycleState?.authorityMode;
|
|
58
|
+
if (Array.isArray(rule.allowedTreeStates) && rule.allowedTreeStates.length > 0 && !rule.allowedTreeStates.includes(treeState)) {
|
|
59
|
+
return {
|
|
60
|
+
ok: false,
|
|
61
|
+
reason: "This skill is not allowed in the current lifecycle state",
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
if (Array.isArray(rule.allowedAuthorityModes) && rule.allowedAuthorityModes.length > 0 && !rule.allowedAuthorityModes.includes(authorityMode)) {
|
|
65
|
+
return {
|
|
66
|
+
ok: false,
|
|
67
|
+
reason: "This skill is not allowed in the current authority mode",
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
ok: true,
|
|
72
|
+
reason: "Skill prerequisites are satisfied by the current project-local truth",
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
function evaluateSkillReadiness(skillId, doctorResult) {
|
|
76
|
+
if (!doctorResult.ok || !doctorResult.handoffReadiness.ok) {
|
|
77
|
+
return {
|
|
78
|
+
ok: false,
|
|
79
|
+
reason: "Bootstrap or handoff validation is failing; repair doctor errors before exporting handoff payloads",
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
if (skillId === "spec_reconstruction") {
|
|
83
|
+
return {
|
|
84
|
+
ok: true,
|
|
85
|
+
reason: "Projects may delegate spec reconstruction to an external AI host when canonical tree work is needed",
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
const rule = (doctorResult.commandGating?.entries ?? []).find((entry) => entry.command === "handoff" && entry.skill === skillId) ?? null;
|
|
89
|
+
return commandRuleAllowsCurrentState(rule, doctorResult);
|
|
90
|
+
}
|
|
91
|
+
function getSkillSpecificExpectations(
|
|
92
|
+
skillId,
|
|
93
|
+
resultContractRef,
|
|
94
|
+
specContract,
|
|
95
|
+
auditContract,
|
|
96
|
+
auditSweepContract,
|
|
97
|
+
auditExecutionArtifacts,
|
|
98
|
+
highRiskSchemaContracts,
|
|
99
|
+
externalExecutionArtifacts,
|
|
100
|
+
) {
|
|
101
|
+
if (skillId === "spec_reconstruction") {
|
|
102
|
+
return {
|
|
103
|
+
compareTargets: [".nimi/spec"],
|
|
104
|
+
closeoutSummaryFields: specContract.summaryRequiredFields,
|
|
105
|
+
closeoutSummaryStatus: specContract.summaryStatusEnum,
|
|
106
|
+
executionSchemaRefs: [],
|
|
107
|
+
artifactRoots: {},
|
|
108
|
+
expectedArtifactKinds: [],
|
|
109
|
+
skillExpectedResults: [
|
|
110
|
+
"generate_canonical_tree_under_.nimi/spec",
|
|
111
|
+
"establish_kernel_markdown_and_kernel_tables_before_generated_views_or_guides",
|
|
112
|
+
"record_file_level_generation_audit_under_.nimi/spec/_meta/spec-generation-audit.yaml",
|
|
113
|
+
`satisfy_canonical_tree_completion_declared_in_${resultContractRef}`,
|
|
114
|
+
],
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
if (skillId === "doc_spec_audit") {
|
|
118
|
+
return {
|
|
119
|
+
compareTargets: auditContract.defaultComparedPaths,
|
|
120
|
+
closeoutSummaryFields: auditContract.summaryRequiredFields,
|
|
121
|
+
closeoutSummaryStatus: auditContract.summaryStatusEnum,
|
|
122
|
+
executionSchemaRefs: [],
|
|
123
|
+
artifactRoots: {},
|
|
124
|
+
expectedArtifactKinds: [],
|
|
125
|
+
skillExpectedResults: [
|
|
126
|
+
`compare_${auditContract.defaultComparedPaths.join("_and_")}_against_.nimi/spec_truth`,
|
|
127
|
+
`return_local_only_summary_that_satisfies_${resultContractRef}`,
|
|
128
|
+
],
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
if (skillId === "audit_sweep") {
|
|
132
|
+
return {
|
|
133
|
+
compareTargets: [".nimi/spec", ".nimi/contracts", ".nimi/methodology"],
|
|
134
|
+
closeoutSummaryFields: auditSweepContract.summaryRequiredFields,
|
|
135
|
+
closeoutSummaryStatus: auditSweepContract.summaryStatusEnum,
|
|
136
|
+
executionSchemaRefs: [],
|
|
137
|
+
artifactRoots: auditExecutionArtifacts.artifactRoots ?? {},
|
|
138
|
+
expectedArtifactKinds: [
|
|
139
|
+
"audit-plan",
|
|
140
|
+
"audit-chunk",
|
|
141
|
+
"audit-ledger",
|
|
142
|
+
"audit-report",
|
|
143
|
+
"audit-remediation-map",
|
|
144
|
+
"audit-packet",
|
|
145
|
+
"audit-run-ledger",
|
|
146
|
+
"audit-closeout",
|
|
147
|
+
],
|
|
148
|
+
skillExpectedResults: [
|
|
149
|
+
"freeze_full_coverage_inventory_for_a_declared_target_root",
|
|
150
|
+
"partition_inventory_into_machine_identifiable_audit_chunks",
|
|
151
|
+
`return_local_only_audit_sweep_summary_that_satisfies_${resultContractRef}`,
|
|
152
|
+
"produce_frozen_findings_ledger_and_remediation_map_without_claiming_semantic_closure",
|
|
153
|
+
],
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
if (skillId === "high_risk_execution") {
|
|
157
|
+
const executionSchemaRefs = highRiskSchemaContracts.map((entry) => entry.path);
|
|
158
|
+
return {
|
|
159
|
+
compareTargets: [".nimi/spec", ".nimi/contracts"],
|
|
160
|
+
closeoutSummaryFields: [
|
|
161
|
+
"packet_ref",
|
|
162
|
+
"orchestration_state_ref",
|
|
163
|
+
"prompt_ref",
|
|
164
|
+
"worker_output_ref",
|
|
165
|
+
"evidence_refs",
|
|
166
|
+
"status",
|
|
167
|
+
"summary",
|
|
168
|
+
"verified_at",
|
|
169
|
+
],
|
|
170
|
+
closeoutSummaryStatus: [
|
|
171
|
+
"candidate_ready",
|
|
172
|
+
"blocked",
|
|
173
|
+
"failed",
|
|
174
|
+
],
|
|
175
|
+
executionSchemaRefs,
|
|
176
|
+
artifactRoots: externalExecutionArtifacts.artifactRoots ?? {},
|
|
177
|
+
expectedArtifactKinds: [
|
|
178
|
+
"execution-packet",
|
|
179
|
+
"orchestration-state",
|
|
180
|
+
"prompt",
|
|
181
|
+
"worker-output",
|
|
182
|
+
"acceptance",
|
|
183
|
+
],
|
|
184
|
+
skillExpectedResults: [
|
|
185
|
+
"use_seed_only_execution_contracts_without_claiming_runtime_ownership",
|
|
186
|
+
"produce_packetized_high_risk_execution_artifacts_only_if_the_change_requires_methodology",
|
|
187
|
+
`return_local_only_external_execution_summary_that_satisfies_${resultContractRef}`,
|
|
188
|
+
],
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
return {
|
|
192
|
+
compareTargets: [],
|
|
193
|
+
closeoutSummaryFields: [],
|
|
194
|
+
closeoutSummaryStatus: [],
|
|
195
|
+
executionSchemaRefs: [],
|
|
196
|
+
artifactRoots: {},
|
|
197
|
+
expectedArtifactKinds: [],
|
|
198
|
+
skillExpectedResults: [],
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
export async function buildHandoffPayload(projectRoot, skillId) {
|
|
202
|
+
const doctorResult = await inspectDoctorState(projectRoot);
|
|
203
|
+
const manifestText = await readTextIfFile(path.join(projectRoot, ".nimi", "config", "skill-manifest.yaml"));
|
|
204
|
+
const skillsConfigText = await readTextIfFile(path.join(projectRoot, ".nimi", "config", "skills.yaml"));
|
|
205
|
+
const handoffText = await readTextIfFile(path.join(projectRoot, ".nimi", "methodology", "skill-handoff.yaml"));
|
|
206
|
+
const specReconstructionText = await readTextIfFile(path.join(projectRoot, ".nimi", "methodology", "spec-reconstruction.yaml"));
|
|
207
|
+
const specReconstructionDocument = parseYamlText(specReconstructionText);
|
|
208
|
+
const specContract = await loadSpecReconstructionContract(projectRoot);
|
|
209
|
+
const specTreeModel = await loadSpecTreeModelContract(projectRoot);
|
|
210
|
+
const specGenerationInputs = await loadSpecGenerationInputsConfig(projectRoot);
|
|
211
|
+
const auditContract = await loadDocSpecAuditContract(projectRoot);
|
|
212
|
+
const auditSweepContract = await loadAuditSweepContract(projectRoot);
|
|
213
|
+
const hostCompatibilityContract = await loadExternalHostCompatibilityContract(projectRoot);
|
|
214
|
+
const auditExecutionArtifacts = await loadAuditExecutionArtifactsConfig(projectRoot);
|
|
215
|
+
const externalExecutionArtifacts = await loadExternalExecutionArtifactsConfig(projectRoot);
|
|
216
|
+
const highRiskSchemaContracts = await loadHighRiskSchemaContracts(projectRoot);
|
|
217
|
+
const manifestSkills = parseSkillSection(manifestText, "skills");
|
|
218
|
+
const expectedSkills = parseSkillSection(skillsConfigText, "expected_skill_surfaces");
|
|
219
|
+
const manifestSkill = manifestSkills.find((skill) => skill.id === skillId) ?? null;
|
|
220
|
+
const expectedSkill = expectedSkills.find((skill) => skill.id === skillId) ?? null;
|
|
221
|
+
if (!manifestSkill || !expectedSkill) {
|
|
222
|
+
return {
|
|
223
|
+
ok: false,
|
|
224
|
+
exitCode: 1,
|
|
225
|
+
error: `Unknown or undeclared skill id: ${skillId}`,
|
|
226
|
+
availableSkills: manifestSkills.map((skill) => skill.id),
|
|
227
|
+
doctor: doctorResult,
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
const readiness = evaluateSkillReadiness(skillId, doctorResult);
|
|
231
|
+
const resultContractRef = manifestSkill.result_contract_ref ?? SKILL_RESULT_CONTRACT_REFS[skillId] ?? null;
|
|
232
|
+
const handoffContextOrder = readYamlList(handoffText, "required_context_order");
|
|
233
|
+
const skillInputs = manifestSkill.inputs ?? [];
|
|
234
|
+
const orderedContext = mergeOrderedPaths(handoffContextOrder, skillInputs, [resultContractRef]);
|
|
235
|
+
const contextWithBlueprint = doctorResult.blueprintReference?.present
|
|
236
|
+
? mergeOrderedPaths(orderedContext, [doctorResult.blueprintReference.path])
|
|
237
|
+
: orderedContext;
|
|
238
|
+
const hardConstraints = mergeOrderedPaths(
|
|
239
|
+
readYamlList(handoffText, "hard_constraints"),
|
|
240
|
+
skillId === "spec_reconstruction" ? readYamlList(specReconstructionText, "hard_constraints") : [],
|
|
241
|
+
);
|
|
242
|
+
const baseExpectedResults = readYamlList(handoffText, "expected_results");
|
|
243
|
+
const skillSpecific = getSkillSpecificExpectations(
|
|
244
|
+
skillId,
|
|
245
|
+
resultContractRef,
|
|
246
|
+
specContract,
|
|
247
|
+
auditContract,
|
|
248
|
+
auditSweepContract,
|
|
249
|
+
auditExecutionArtifacts,
|
|
250
|
+
highRiskSchemaContracts,
|
|
251
|
+
externalExecutionArtifacts,
|
|
252
|
+
);
|
|
253
|
+
const expectedResults = mergeOrderedPaths(baseExpectedResults, skillSpecific.skillExpectedResults);
|
|
254
|
+
const generationContext = skillId === "spec_reconstruction"
|
|
255
|
+
? {
|
|
256
|
+
canonicalTargetRoot: specGenerationInputs.canonicalTargetRoot ?? specTreeModel.canonicalRoot ?? ".nimi/spec",
|
|
257
|
+
mode: specGenerationInputs.mode ?? "mixed",
|
|
258
|
+
codeRoots: specGenerationInputs.codeRoots ?? [],
|
|
259
|
+
docsRoots: specGenerationInputs.docsRoots ?? [],
|
|
260
|
+
structureRoots: specGenerationInputs.structureRoots ?? [],
|
|
261
|
+
humanNotePaths: specGenerationInputs.humanNotePaths ?? [],
|
|
262
|
+
benchmarkBlueprintRoot: specGenerationInputs.benchmarkBlueprintRoot ?? doctorResult.blueprintReference?.root ?? null,
|
|
263
|
+
benchmarkMode: specGenerationInputs.benchmarkMode ?? doctorResult.blueprintReference?.mode ?? "none",
|
|
264
|
+
acceptanceMode: specGenerationInputs.acceptanceMode ?? "canonical_tree_validity_without_blueprint",
|
|
265
|
+
generationOrder: specGenerationInputs.generationOrder ?? [],
|
|
266
|
+
inferenceRules: specGenerationInputs.inferenceRules ?? [],
|
|
267
|
+
auditRef: SPEC_GENERATION_AUDIT_REF,
|
|
268
|
+
auditContractRef: SPEC_GENERATION_AUDIT_CONTRACT_REF,
|
|
269
|
+
requiredFileClasses: [
|
|
270
|
+
"INDEX.md",
|
|
271
|
+
"domain kernel/*.md",
|
|
272
|
+
"domain kernel/tables/**",
|
|
273
|
+
],
|
|
274
|
+
optionalFileClasses: [
|
|
275
|
+
"domain kernel/generated/**",
|
|
276
|
+
"thin domain guides",
|
|
277
|
+
],
|
|
278
|
+
minimalRequiredOutputs: Array.isArray(specReconstructionDocument?.reconstruction?.target_tree_shape?.minimal_required_outputs)
|
|
279
|
+
? specReconstructionDocument.reconstruction.target_tree_shape.minimal_required_outputs.map((entry) => String(entry))
|
|
280
|
+
: [],
|
|
281
|
+
minimumGenerationSequence: Array.isArray(specReconstructionDocument?.reconstruction?.target_tree_shape?.minimum_generation_sequence)
|
|
282
|
+
? specReconstructionDocument.reconstruction.target_tree_shape.minimum_generation_sequence.map((entry) => String(entry))
|
|
283
|
+
: [],
|
|
284
|
+
skeletonRules: Array.isArray(specReconstructionDocument?.reconstruction?.target_tree_shape?.skeleton_rules)
|
|
285
|
+
? specReconstructionDocument.reconstruction.target_tree_shape.skeleton_rules.map((entry) => String(entry))
|
|
286
|
+
: [],
|
|
287
|
+
}
|
|
288
|
+
: null;
|
|
289
|
+
return {
|
|
290
|
+
contractVersion: HANDOFF_PAYLOAD_CONTRACT_VERSION,
|
|
291
|
+
ok: readiness.ok,
|
|
292
|
+
exitCode: readiness.ok ? 0 : 1,
|
|
293
|
+
projectRoot,
|
|
294
|
+
handoffSurface: {
|
|
295
|
+
authoritativeMode: "json",
|
|
296
|
+
promptMode: "human_projection_only",
|
|
297
|
+
hostStrategy: "host_agnostic_external_host",
|
|
298
|
+
hostCompatibilityRef: EXTERNAL_HOST_COMPATIBILITY_CONTRACT_REF,
|
|
299
|
+
supportedHostPosture: hostCompatibilityContract.supportedHostPosture ?? [],
|
|
300
|
+
supportedHostExamples: hostCompatibilityContract.supportedHostExamples ?? [],
|
|
301
|
+
requiredHostBehavior: hostCompatibilityContract.requiredBehavior ?? [],
|
|
302
|
+
forbiddenHostBehavior: hostCompatibilityContract.forbiddenBehavior ?? [],
|
|
303
|
+
hostCompatibilitySummary: doctorResult.hostCompatibility ?? {
|
|
304
|
+
contractRef: EXTERNAL_HOST_COMPATIBILITY_CONTRACT_REF,
|
|
305
|
+
supportedHostPosture: hostCompatibilityContract.supportedHostPosture ?? [],
|
|
306
|
+
supportedHostExamples: hostCompatibilityContract.supportedHostExamples ?? [],
|
|
307
|
+
requiredBehavior: hostCompatibilityContract.requiredBehavior ?? [],
|
|
308
|
+
forbiddenBehavior: hostCompatibilityContract.forbiddenBehavior ?? [],
|
|
309
|
+
genericExternalHostCompatible: false,
|
|
310
|
+
namedOverlaySupport: {
|
|
311
|
+
mode: "generic_only",
|
|
312
|
+
admittedOverlayIds: [],
|
|
313
|
+
selectedOverlayId: null,
|
|
314
|
+
selectedOverlayProfileRef: null,
|
|
315
|
+
selectedOverlayHostClass: null,
|
|
316
|
+
},
|
|
317
|
+
futureOnlyHostSurfaces: [],
|
|
318
|
+
nativeReviewSurfaces: [],
|
|
319
|
+
},
|
|
320
|
+
},
|
|
321
|
+
runtimeOwner: doctorResult.delegatedContracts.runtimeOwner,
|
|
322
|
+
triggerMode: doctorResult.delegatedContracts.triggerMode,
|
|
323
|
+
handoffReady: doctorResult.handoffReadiness.ok,
|
|
324
|
+
skill: {
|
|
325
|
+
id: skillId,
|
|
326
|
+
required: expectedSkill.required === "true",
|
|
327
|
+
source: manifestSkill.source ?? "external",
|
|
328
|
+
purpose: expectedSkill.purpose ?? null,
|
|
329
|
+
inputs: skillInputs,
|
|
330
|
+
resultContractRef,
|
|
331
|
+
compareTargets: skillSpecific.compareTargets,
|
|
332
|
+
expectedCloseoutSummaryFields: skillSpecific.closeoutSummaryFields,
|
|
333
|
+
expectedCloseoutSummaryStatus: skillSpecific.closeoutSummaryStatus,
|
|
334
|
+
executionSchemaRefs: skillSpecific.executionSchemaRefs,
|
|
335
|
+
expectedArtifactRoots: skillSpecific.artifactRoots,
|
|
336
|
+
expectedArtifactKinds: skillSpecific.expectedArtifactKinds,
|
|
337
|
+
readiness,
|
|
338
|
+
},
|
|
339
|
+
generationContext,
|
|
340
|
+
contracts: {
|
|
341
|
+
handoffRef: ".nimi/methodology/skill-handoff.yaml",
|
|
342
|
+
runtimeContractRef: ".nimi/methodology/skill-runtime.yaml",
|
|
343
|
+
manifestRef: ".nimi/config/skill-manifest.yaml",
|
|
344
|
+
hostProfileRef: ".nimi/config/host-profile.yaml",
|
|
345
|
+
hostAdapterRef: HOST_ADAPTER_CONFIG_REF,
|
|
346
|
+
externalExecutionArtifactsRef: EXTERNAL_EXECUTION_ARTIFACTS_CONFIG_REF,
|
|
347
|
+
installerRef: ".nimi/config/skill-installer.yaml",
|
|
348
|
+
installerResultContractRef: ".nimi/methodology/skill-installer-result.yaml",
|
|
349
|
+
installerSummaryProjectionContractRef: ".nimi/methodology/skill-installer-summary-projection.yaml",
|
|
350
|
+
exchangeProjectionContractRef: ".nimi/methodology/skill-exchange-projection.yaml",
|
|
351
|
+
reconstructionGuidanceRef: ".nimi/methodology/spec-reconstruction.yaml",
|
|
352
|
+
resultContractRef,
|
|
353
|
+
},
|
|
354
|
+
context: {
|
|
355
|
+
orderedPaths: contextWithBlueprint,
|
|
356
|
+
handoffRequiredContextOrder: handoffContextOrder,
|
|
357
|
+
skillInputs,
|
|
358
|
+
},
|
|
359
|
+
adapter: {
|
|
360
|
+
selectedId: doctorResult.delegatedContracts.selectedAdapterId,
|
|
361
|
+
admittedIds: doctorResult.delegatedContracts.admittedAdapterIds,
|
|
362
|
+
handoffMode: doctorResult.delegatedContracts.adapterHandoffMode,
|
|
363
|
+
semanticReviewOwner: doctorResult.delegatedContracts.semanticReviewOwner,
|
|
364
|
+
profileRef: doctorResult.adapterProfiles.selected?.profileRef ?? null,
|
|
365
|
+
hostClass: doctorResult.adapterProfiles.selected?.hostClass ?? null,
|
|
366
|
+
upstreamSeedProfile: doctorResult.adapterProfiles.selected?.upstreamSeedProfile ?? null,
|
|
367
|
+
purpose: doctorResult.adapterProfiles.selected?.purpose ?? null,
|
|
368
|
+
operationalOwner: doctorResult.adapterProfiles.selected?.operationalOwner ?? [],
|
|
369
|
+
currentGaps: doctorResult.adapterProfiles.selected?.currentGaps ?? [],
|
|
370
|
+
futureSurface: doctorResult.adapterProfiles.selected?.promptHandoff?.futureSurface ?? [],
|
|
371
|
+
futureSurfaceStatus: doctorResult.adapterProfiles.selected?.promptHandoff?.futureSurfaceStatus ?? null,
|
|
372
|
+
nativeReviewBoundary: doctorResult.adapterProfiles.selected?.nativeReviewBoundary ?? null,
|
|
373
|
+
admittedProfiles: doctorResult.adapterProfiles.admitted,
|
|
374
|
+
},
|
|
375
|
+
constraints: hardConstraints,
|
|
376
|
+
expectedResults,
|
|
377
|
+
doctor: {
|
|
378
|
+
ok: doctorResult.ok,
|
|
379
|
+
handoffReadiness: doctorResult.handoffReadiness,
|
|
380
|
+
delegatedContracts: doctorResult.delegatedContracts,
|
|
381
|
+
auditArtifact: doctorResult.auditArtifact,
|
|
382
|
+
highRiskSchemaContracts: highRiskSchemaContracts.map((entry) => ({
|
|
383
|
+
path: entry.path,
|
|
384
|
+
ok: entry.ok,
|
|
385
|
+
})),
|
|
386
|
+
},
|
|
387
|
+
nextAction: readiness.ok
|
|
388
|
+
? `Delegate explicit skill execution for \`${skillId}\` to ${doctorResult.delegatedContracts.runtimeOwner}.`
|
|
389
|
+
: readiness.reason,
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
export function formatHandoffPayload(payload) {
|
|
393
|
+
const lines = [
|
|
394
|
+
styleHeading(`nimicoding handoff: ${payload.projectRoot}`),
|
|
395
|
+
"",
|
|
396
|
+
styleLabel(localize("Skill:", "Skill:")),
|
|
397
|
+
` - id: ${payload.skill.id}`,
|
|
398
|
+
` - required: ${payload.skill.required ? "true" : "false"}`,
|
|
399
|
+
` - source: ${payload.skill.source}`,
|
|
400
|
+
` - purpose: ${payload.skill.purpose ?? localize("unknown", "未知")}`,
|
|
401
|
+
` - result_contract_ref: ${payload.skill.resultContractRef ?? "none"}`,
|
|
402
|
+
` - ready: ${styleStatus(payload.skill.readiness.ok ? "ready" : "needs_attention")}`,
|
|
403
|
+
"",
|
|
404
|
+
styleLabel(localize("Runtime:", "运行时:")),
|
|
405
|
+
` - owner: ${payload.runtimeOwner ?? localize("unknown", "未知")}`,
|
|
406
|
+
` - trigger_mode: ${payload.triggerMode ?? localize("unknown", "未知")}`,
|
|
407
|
+
` - handoff_ready: ${payload.handoffReady ? "true" : "false"}`,
|
|
408
|
+
` - authoritative_mode: ${payload.handoffSurface.authoritativeMode}`,
|
|
409
|
+
` - prompt_mode: ${payload.handoffSurface.promptMode}`,
|
|
410
|
+
` - host_compatibility_ref: ${payload.handoffSurface.hostCompatibilityRef}`,
|
|
411
|
+
` - supported_host_posture: ${payload.handoffSurface.supportedHostPosture.join(", ") || "none"}`,
|
|
412
|
+
` - generic_external_host_compatible: ${payload.handoffSurface.hostCompatibilitySummary.genericExternalHostCompatible ? "true" : "false"}`,
|
|
413
|
+
` - named_overlay_mode: ${payload.handoffSurface.hostCompatibilitySummary.namedOverlaySupport.mode}`,
|
|
414
|
+
"",
|
|
415
|
+
styleLabel(localize("Adapter:", "Adapter:")),
|
|
416
|
+
` - selected_id: ${payload.adapter.selectedId ?? localize("unknown", "未知")}`,
|
|
417
|
+
` - admitted_ids: ${payload.adapter.admittedIds.length}`,
|
|
418
|
+
` - handoff_mode: ${payload.adapter.handoffMode ?? localize("unknown", "未知")}`,
|
|
419
|
+
` - semantic_review_owner: ${payload.adapter.semanticReviewOwner ?? localize("unknown", "未知")}`,
|
|
420
|
+
` - selected_profile_ref: ${payload.adapter.profileRef ?? "none"}`,
|
|
421
|
+
"",
|
|
422
|
+
styleLabel(localize("Context:", "上下文:")),
|
|
423
|
+
` - ordered_paths: ${payload.context.orderedPaths.length}`,
|
|
424
|
+
` - skill_inputs: ${payload.context.skillInputs.length}`,
|
|
425
|
+
"",
|
|
426
|
+
];
|
|
427
|
+
if (payload.generationContext) {
|
|
428
|
+
lines.push(
|
|
429
|
+
styleLabel(localize("Generation:", "生成:")),
|
|
430
|
+
` - target_root: ${payload.generationContext.canonicalTargetRoot}`,
|
|
431
|
+
` - mode: ${payload.generationContext.mode}`,
|
|
432
|
+
` - benchmark_root: ${payload.generationContext.benchmarkBlueprintRoot ?? "none"}`,
|
|
433
|
+
` - acceptance_mode: ${payload.generationContext.acceptanceMode}`,
|
|
434
|
+
` - audit_ref: ${payload.generationContext.auditRef}`,
|
|
435
|
+
` - code_roots: ${payload.generationContext.codeRoots.length}`,
|
|
436
|
+
` - docs_roots: ${payload.generationContext.docsRoots.length}`,
|
|
437
|
+
` - structure_roots: ${payload.generationContext.structureRoots.length}`,
|
|
438
|
+
"",
|
|
439
|
+
);
|
|
440
|
+
}
|
|
441
|
+
lines.push(
|
|
442
|
+
styleLabel(localize("Next:", "下一步:")),
|
|
443
|
+
` - ${localize(payload.nextAction, translateHandoffReason(payload.nextAction))}`,
|
|
444
|
+
);
|
|
445
|
+
return `${lines.join("\n")}\n`;
|
|
446
|
+
}
|
|
447
|
+
export function formatHandoffPrompt(payload) {
|
|
448
|
+
const isSpecReconstruction = payload.skill.id === "spec_reconstruction" && payload.generationContext;
|
|
449
|
+
const lines = [
|
|
450
|
+
localize(
|
|
451
|
+
`You are the external AI host responsible for the declared \`${payload.skill.id}\` skill in project \`${payload.projectRoot}\`.`,
|
|
452
|
+
`你是外部 AI host,负责项目 \`${payload.projectRoot}\` 中声明的 \`${payload.skill.id}\` skill。`,
|
|
453
|
+
),
|
|
454
|
+
"",
|
|
455
|
+
localize(
|
|
456
|
+
"Use the JSON handoff payload as the authoritative machine contract.",
|
|
457
|
+
"请将 JSON handoff payload 作为权威机器契约。",
|
|
458
|
+
),
|
|
459
|
+
localize(
|
|
460
|
+
"Treat this prompt as a human-readable projection of that same contract, not as a replacement runtime owner.",
|
|
461
|
+
"请将这份 prompt 视为同一契约的人类可读投影,而不是替代 runtime owner。",
|
|
462
|
+
),
|
|
463
|
+
"",
|
|
464
|
+
localize(
|
|
465
|
+
"This handoff surface is host-agnostic. Any external host may consume it if it respects the declared compatibility contract.",
|
|
466
|
+
"这个 handoff surface 是 host-agnostic 的。任何外部 host 只要遵守声明的兼容契约,都可以消费它。",
|
|
467
|
+
),
|
|
468
|
+
"",
|
|
469
|
+
localize("Read this project-local truth first, in order:", "请优先按顺序阅读这些项目本地 truth:"),
|
|
470
|
+
...payload.context.orderedPaths.map((entry, index) => `${index + 1}. ${entry}`),
|
|
471
|
+
"",
|
|
472
|
+
localize("Operate under these constraints:", "请在以下约束下执行:"),
|
|
473
|
+
...payload.constraints.map((entry) => `- ${entry}`),
|
|
474
|
+
"",
|
|
475
|
+
localize("Expected results:", "预期结果:"),
|
|
476
|
+
...payload.expectedResults.map((entry) => `- ${entry}`),
|
|
477
|
+
"",
|
|
478
|
+
localize("Skill contract:", "Skill 契约:"),
|
|
479
|
+
`- ${localize("Skill id", "Skill id")}: ${payload.skill.id}`,
|
|
480
|
+
`- ${localize("Purpose", "用途")}: ${payload.skill.purpose ?? localize("unknown", "未知")}`,
|
|
481
|
+
`- ${localize("Runtime owner", "Runtime owner")}: ${payload.runtimeOwner ?? localize("unknown", "未知")}`,
|
|
482
|
+
`- ${localize("Trigger mode", "触发模式")}: ${payload.triggerMode ?? localize("unknown", "未知")}`,
|
|
483
|
+
`- ${localize("Result contract", "结果契约")}: ${payload.skill.resultContractRef ?? "none"}`,
|
|
484
|
+
`- ${localize("Host compatibility contract", "Host 兼容契约")}: ${payload.handoffSurface.hostCompatibilityRef}`,
|
|
485
|
+
`- ${localize("Host adapter", "Host adapter")}: ${payload.adapter.selectedId ?? "none"}`,
|
|
486
|
+
`- ${localize("Semantic review owner", "语义审查 owner")}: ${payload.adapter.semanticReviewOwner ?? localize("unknown", "未知")}`,
|
|
487
|
+
];
|
|
488
|
+
if (payload.handoffSurface.supportedHostPosture.length > 0) {
|
|
489
|
+
lines.push(`- ${localize("Supported host posture", "支持的 host 姿态")}: ${payload.handoffSurface.supportedHostPosture.join(", ")}`);
|
|
490
|
+
}
|
|
491
|
+
if (payload.handoffSurface.supportedHostExamples.length > 0) {
|
|
492
|
+
lines.push(`- ${localize("Supported external host examples", "支持的外部 host 示例")}: ${payload.handoffSurface.supportedHostExamples.join(", ")}`);
|
|
493
|
+
}
|
|
494
|
+
if (payload.handoffSurface.requiredHostBehavior.length > 0) {
|
|
495
|
+
lines.push(`- ${localize("Required host behavior", "要求的 host 行为")}: ${payload.handoffSurface.requiredHostBehavior.join(", ")}`);
|
|
496
|
+
}
|
|
497
|
+
if (payload.handoffSurface.forbiddenHostBehavior.length > 0) {
|
|
498
|
+
lines.push(`- ${localize("Forbidden host behavior", "禁止的 host 行为")}: ${payload.handoffSurface.forbiddenHostBehavior.join(", ")}`);
|
|
499
|
+
}
|
|
500
|
+
lines.push(`- ${localize("Generic external host compatible", "通用外部 host 兼容")}: ${payload.handoffSurface.hostCompatibilitySummary.genericExternalHostCompatible ? "true" : "false"}`);
|
|
501
|
+
lines.push(`- ${localize("Named overlay mode", "命名 overlay 模式")}: ${payload.handoffSurface.hostCompatibilitySummary.namedOverlaySupport.mode}`);
|
|
502
|
+
if (payload.handoffSurface.hostCompatibilitySummary.namedOverlaySupport.admittedOverlayIds.length > 0) {
|
|
503
|
+
lines.push(`- ${localize("Admitted named overlays", "已准入的命名 overlay")}: ${payload.handoffSurface.hostCompatibilitySummary.namedOverlaySupport.admittedOverlayIds.join(", ")}`);
|
|
504
|
+
}
|
|
505
|
+
if (payload.handoffSurface.hostCompatibilitySummary.futureOnlyHostSurfaces.length > 0) {
|
|
506
|
+
lines.push(`- ${localize("Future-only host surfaces", "仅未来支持的 host surface")}: ${payload.handoffSurface.hostCompatibilitySummary.futureOnlyHostSurfaces.map((surface) => `${surface.adapterId}:${surface.command}:${surface.status ?? "unknown"}`).join(", ")}`);
|
|
507
|
+
}
|
|
508
|
+
if ((payload.handoffSurface.hostCompatibilitySummary.nativeReviewSurfaces ?? []).length > 0) {
|
|
509
|
+
lines.push(`- ${localize("Native review surfaces", "原生审查 surface")}: ${payload.handoffSurface.hostCompatibilitySummary.nativeReviewSurfaces.map((surface) => `${surface.adapterId}:approval=${surface.approvalReviewScope}:${surface.approvalReviewSemanticEffect},pr=${surface.githubAutoReviewScope}:${surface.githubAutoReviewSemanticEffect}`).join("; ")}`);
|
|
510
|
+
}
|
|
511
|
+
if (payload.adapter.selectedId && payload.adapter.selectedId !== "none") {
|
|
512
|
+
lines.push(`- ${localize("Adapter handoff mode", "Adapter handoff 模式")}: ${payload.adapter.handoffMode ?? localize("unknown", "未知")}`);
|
|
513
|
+
lines.push(`- ${localize("Adapter profile ref", "Adapter profile 引用")}: ${payload.adapter.profileRef ?? localize("unknown", "未知")}`);
|
|
514
|
+
lines.push(`- ${localize("Adapter host class", "Adapter host 类别")}: ${payload.adapter.hostClass ?? localize("unknown", "未知")}`);
|
|
515
|
+
lines.push(`- ${localize("Adapter upstream seed profile", "Adapter 上游 seed profile")}: ${payload.adapter.upstreamSeedProfile ?? localize("unknown", "未知")}`);
|
|
516
|
+
if (payload.adapter.purpose) {
|
|
517
|
+
lines.push(`- ${localize("Adapter purpose", "Adapter 用途")}: ${payload.adapter.purpose}`);
|
|
518
|
+
}
|
|
519
|
+
if (payload.adapter.operationalOwner.length > 0) {
|
|
520
|
+
lines.push(`- ${localize("Adapter operational owner roots", "Adapter operational owner 根")}: ${payload.adapter.operationalOwner.join(", ")}`);
|
|
521
|
+
}
|
|
522
|
+
if (payload.adapter.currentGaps.length > 0) {
|
|
523
|
+
lines.push(`- ${localize("Adapter current gaps", "Adapter 当前缺口")}: ${payload.adapter.currentGaps.join(", ")}`);
|
|
524
|
+
}
|
|
525
|
+
if (payload.adapter.futureSurface.length > 0) {
|
|
526
|
+
lines.push(`- ${localize("Adapter future-only surfaces", "Adapter 仅未来支持的 surface")}: ${payload.adapter.futureSurface.join(", ")}`);
|
|
527
|
+
lines.push(`- ${localize("Adapter future-only surface status", "Adapter 仅未来 surface 状态")}: ${payload.adapter.futureSurfaceStatus ?? localize("unknown", "未知")}`);
|
|
528
|
+
}
|
|
529
|
+
if (payload.adapter.nativeReviewBoundary) {
|
|
530
|
+
lines.push(`- ${localize("Adapter native review boundary", "Adapter 原生审查边界")}: approval=${payload.adapter.nativeReviewBoundary.approvalReview.scope}:${payload.adapter.nativeReviewBoundary.approvalReview.semanticEffect}; pr=${payload.adapter.nativeReviewBoundary.githubAutoReview.scope}:${payload.adapter.nativeReviewBoundary.githubAutoReview.semanticEffect}`);
|
|
531
|
+
lines.push(`- ${localize("Adapter forbidden semantic substitutions", "Adapter 禁止替代的语义动作")}: ${payload.adapter.nativeReviewBoundary.forbiddenSemanticSubstitutions.join(", ")}`);
|
|
532
|
+
}
|
|
533
|
+
lines.push(localize(
|
|
534
|
+
"- The adapter may route execution, but it must not decide semantic acceptance or final disposition.",
|
|
535
|
+
"- Adapter 可以路由执行,但不得决定语义验收或最终 disposition。",
|
|
536
|
+
));
|
|
537
|
+
}
|
|
538
|
+
if (payload.skill.compareTargets.length > 0) {
|
|
539
|
+
lines.push(`- ${localize("Compare targets", "比较目标")}: ${payload.skill.compareTargets.join(", ")}`);
|
|
540
|
+
}
|
|
541
|
+
if (isSpecReconstruction) {
|
|
542
|
+
lines.push(`- ${localize("Canonical target root", "Canonical 目标根")}: ${payload.generationContext.canonicalTargetRoot}`);
|
|
543
|
+
lines.push(`- ${localize("Generation mode", "生成模式")}: ${payload.generationContext.mode}`);
|
|
544
|
+
lines.push(`- ${localize("Required file classes", "必需文件类别")}: ${payload.generationContext.requiredFileClasses.join(", ")}`);
|
|
545
|
+
lines.push(`- ${localize("Optional file classes", "可选文件类别")}: ${payload.generationContext.optionalFileClasses.join(", ")}`);
|
|
546
|
+
lines.push(`- ${localize("Code roots", "代码根")}: ${payload.generationContext.codeRoots.join(", ") || "none"}`);
|
|
547
|
+
lines.push(`- ${localize("Docs roots", "文档根")}: ${payload.generationContext.docsRoots.join(", ") || "none"}`);
|
|
548
|
+
lines.push(`- ${localize("Structure roots", "结构根")}: ${payload.generationContext.structureRoots.join(", ") || "none"}`);
|
|
549
|
+
lines.push(`- ${localize("Human note paths", "人工说明路径")}: ${payload.generationContext.humanNotePaths.join(", ") || "none"}`);
|
|
550
|
+
lines.push(`- ${localize("Benchmark blueprint root", "Benchmark blueprint root")}: ${payload.generationContext.benchmarkBlueprintRoot ?? "none"}`);
|
|
551
|
+
lines.push(`- ${localize("Acceptance rule", "验收规则")}: ${payload.generationContext.acceptanceMode}`);
|
|
552
|
+
lines.push(`- ${localize("Generation order", "生成顺序")}: ${payload.generationContext.generationOrder.join(", ")}`);
|
|
553
|
+
lines.push(`- ${localize("Audit output", "审计输出")}: ${payload.generationContext.auditRef}`);
|
|
554
|
+
if (payload.generationContext.minimumGenerationSequence.length > 0) {
|
|
555
|
+
lines.push(`- ${localize("Minimum generation sequence", "最小生成顺序")}: ${payload.generationContext.minimumGenerationSequence.join(", ")}`);
|
|
556
|
+
}
|
|
557
|
+
if (payload.generationContext.skeletonRules.length > 0) {
|
|
558
|
+
lines.push(`- ${localize("Skeleton rules", "骨架规则")}: ${payload.generationContext.skeletonRules.join(", ")}`);
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
if (payload.skill.expectedCloseoutSummaryFields.length > 0) {
|
|
562
|
+
lines.push(`- ${localize("Expected closeout summary fields", "预期 closeout summary 字段")}: ${payload.skill.expectedCloseoutSummaryFields.join(", ")}`);
|
|
563
|
+
}
|
|
564
|
+
if (payload.skill.expectedCloseoutSummaryStatus.length > 0) {
|
|
565
|
+
lines.push(`- ${localize("Expected closeout summary status", "预期 closeout summary 状态")}: ${payload.skill.expectedCloseoutSummaryStatus.join(", ")}`);
|
|
566
|
+
}
|
|
567
|
+
if (payload.skill.executionSchemaRefs.length > 0) {
|
|
568
|
+
lines.push(`- ${localize("Execution schema refs", "执行 schema 引用")}: ${payload.skill.executionSchemaRefs.join(", ")}`);
|
|
569
|
+
}
|
|
570
|
+
if (Object.keys(payload.skill.expectedArtifactRoots).length > 0) {
|
|
571
|
+
lines.push(`- ${localize("Expected local artifact roots", "预期本地产物根路径")}: ${Object.entries(payload.skill.expectedArtifactRoots).map(([field, value]) => `${field}=${value}`).join("; ")}`);
|
|
572
|
+
}
|
|
573
|
+
if (payload.skill.expectedArtifactKinds.length > 0) {
|
|
574
|
+
lines.push(`- ${localize("Expected artifact kinds", "预期产物类型")}: ${payload.skill.expectedArtifactKinds.join(", ")}`);
|
|
575
|
+
}
|
|
576
|
+
lines.push(
|
|
577
|
+
"",
|
|
578
|
+
localize("Rules:", "规则:"),
|
|
579
|
+
localize("- Do not assume local skill installation or self-hosting.", "- 不要假设存在本地 skill 安装或 self-hosting。"),
|
|
580
|
+
localize("- Fail closed on unresolved authority, missing context, or contract drift.", "- 在 authority 未解决、上下文缺失或契约漂移时必须 fail-close。"),
|
|
581
|
+
localize("- Treat `.nimi/**` as the primary truth surface.", "- 将 `.nimi/**` 视为主要 truth surface。"),
|
|
582
|
+
);
|
|
583
|
+
if (isSpecReconstruction) {
|
|
584
|
+
lines.push(localize(
|
|
585
|
+
"- Build the canonical tree directly under `.nimi/spec/**`; do not produce a compact summary as the primary output.",
|
|
586
|
+
"- 直接在 `.nimi/spec/**` 下构建 canonical tree;不要把 compact summary 当作主要输出。",
|
|
587
|
+
));
|
|
588
|
+
lines.push(localize(
|
|
589
|
+
"- Write `.nimi/spec/_meta/spec-generation-audit.yaml` alongside the canonical tree. Every generated canonical file must record source refs, source basis, and unresolved gaps.",
|
|
590
|
+
"- 在 canonical tree 旁边写出 `.nimi/spec/_meta/spec-generation-audit.yaml`。每个生成的 canonical 文件都必须记录 source refs、source basis 和未解决缺口。",
|
|
591
|
+
));
|
|
592
|
+
lines.push(localize(
|
|
593
|
+
"- When a benchmark blueprint root is declared, aim for semantic and structural parity with that benchmark rather than byte-for-byte duplication.",
|
|
594
|
+
"- 当声明了 benchmark blueprint root 时,目标应是与该 benchmark 保持语义和结构等价,而不是逐字节复制。",
|
|
595
|
+
));
|
|
596
|
+
lines.push(localize(
|
|
597
|
+
"- For ordinary projects without a benchmark blueprint, infer the domain set from the declared mixed inputs and build kernel markdown/tables first.",
|
|
598
|
+
"- 对于没有 benchmark blueprint 的普通项目,请根据声明的 mixed inputs 推断域集合,并优先生成 kernel markdown/tables。",
|
|
599
|
+
));
|
|
600
|
+
}
|
|
601
|
+
lines.push("", localize(payload.nextAction, translateHandoffReason(payload.nextAction)));
|
|
602
|
+
return `${lines.join("\n")}\n`;
|
|
603
|
+
}
|
|
604
|
+
export const START_HOST_OPTIONS = [
|
|
605
|
+
{
|
|
606
|
+
id: "generic",
|
|
607
|
+
label: "Generic external host",
|
|
608
|
+
zhLabel: "通用外部 Host",
|
|
609
|
+
description: "works with any compatible AI tool",
|
|
610
|
+
zhDescription: "适用于任意兼容的 AI 工具",
|
|
611
|
+
},
|
|
612
|
+
{
|
|
613
|
+
id: "codex",
|
|
614
|
+
label: "Codex",
|
|
615
|
+
zhLabel: "Codex",
|
|
616
|
+
description: "optimized wording for Codex-style coding hosts",
|
|
617
|
+
zhDescription: "为 Codex 风格编码宿主优化措辞",
|
|
618
|
+
},
|
|
619
|
+
{
|
|
620
|
+
id: "claude",
|
|
621
|
+
label: "Claude",
|
|
622
|
+
zhLabel: "Claude",
|
|
623
|
+
description: "optimized wording for Claude-style coding hosts",
|
|
624
|
+
zhDescription: "为 Claude 风格编码宿主优化措辞",
|
|
625
|
+
},
|
|
626
|
+
{
|
|
627
|
+
id: "oh-my-codex",
|
|
628
|
+
label: "oh-my-codex",
|
|
629
|
+
zhLabel: "oh-my-codex",
|
|
630
|
+
description: "includes the admitted OMX execution boundary reminder",
|
|
631
|
+
zhDescription: "附带已准入 OMX 执行边界提醒",
|
|
632
|
+
},
|
|
633
|
+
];
|
|
634
|
+
export function getStartHostOption(hostId) {
|
|
635
|
+
return START_HOST_OPTIONS.find((option) => option.id === hostId) ?? null;
|
|
636
|
+
}
|
|
637
|
+
export function resolveStartHostChoice(explicitHost, payload) {
|
|
638
|
+
if (explicitHost) {
|
|
639
|
+
return explicitHost;
|
|
640
|
+
}
|
|
641
|
+
if (payload.adapter.selectedId === "oh_my_codex") {
|
|
642
|
+
return "oh-my-codex";
|
|
643
|
+
}
|
|
644
|
+
if (payload.adapter.selectedId === "codex") {
|
|
645
|
+
return "codex";
|
|
646
|
+
}
|
|
647
|
+
if (payload.adapter.selectedId === "claude") {
|
|
648
|
+
return "claude";
|
|
649
|
+
}
|
|
650
|
+
return "generic";
|
|
651
|
+
}
|
|
652
|
+
export function formatStartPastePrompt(payload, options) {
|
|
653
|
+
const hostId = options.hostId ?? "generic";
|
|
654
|
+
const jsonRef = options.jsonRef;
|
|
655
|
+
const host = getStartHostOption(hostId) ?? getStartHostOption("generic");
|
|
656
|
+
const hostHeading = {
|
|
657
|
+
generic: localize("Task package for external AI host", "面向外部 AI Host 的任务包"),
|
|
658
|
+
codex: localize("Task package for Codex", "面向 Codex 的任务包"),
|
|
659
|
+
claude: localize("Task package for Claude", "面向 Claude 的任务包"),
|
|
660
|
+
"oh-my-codex": localize("Task package for oh-my-codex", "面向 oh-my-codex 的任务包"),
|
|
661
|
+
}[hostId] ?? localize("Task package for external AI host", "面向外部 AI Host 的任务包");
|
|
662
|
+
const lines = [
|
|
663
|
+
`${hostHeading}:`,
|
|
664
|
+
localize(`- Task: \`${payload.skill.id}\``, `- 任务:\`${payload.skill.id}\``),
|
|
665
|
+
localize(`- Task file: \`${jsonRef}\``, `- 任务文件:\`${jsonRef}\``),
|
|
666
|
+
localize(`- Output format: \`${payload.skill.resultContractRef ?? "the declared result contract"}\``, `- 输出格式:\`${payload.skill.resultContractRef ?? "声明的结果契约"}\``),
|
|
667
|
+
localize(
|
|
668
|
+
"Use this order:",
|
|
669
|
+
"执行顺序:",
|
|
670
|
+
),
|
|
671
|
+
localize(
|
|
672
|
+
`1. Read \`${jsonRef}\` first. This file is the source of truth for this task.`,
|
|
673
|
+
`1. 先读取 \`${jsonRef}\`。这个文件是该任务的权威来源。`,
|
|
674
|
+
),
|
|
675
|
+
localize(
|
|
676
|
+
"2. Use the ordered context paths, constraints, and result requirements declared there.",
|
|
677
|
+
"2. 按其中声明的上下文顺序、约束和结果要求执行。",
|
|
678
|
+
),
|
|
679
|
+
localize(
|
|
680
|
+
"3. Treat `.nimi/**` as the main source of project rules. If required context is missing or inconsistent, stop and report it.",
|
|
681
|
+
"3. 将 `.nimi/**` 视为项目规则的主要来源。如果缺少必要上下文或内容不一致,就停止并报告。",
|
|
682
|
+
),
|
|
683
|
+
localize(
|
|
684
|
+
`4. Complete \`${payload.skill.id}\` and return the result in \`${payload.skill.resultContractRef ?? "the declared result contract"}\` format.`,
|
|
685
|
+
`4. 完成 \`${payload.skill.id}\`,并按 \`${payload.skill.resultContractRef ?? "声明的结果契约"}\` 格式返回结果。`,
|
|
686
|
+
),
|
|
687
|
+
];
|
|
688
|
+
if (payload.skill.id === "spec_reconstruction" && payload.generationContext) {
|
|
689
|
+
lines.splice(3, 0, localize(
|
|
690
|
+
`- Canonical target root: \`${payload.generationContext.canonicalTargetRoot}\``,
|
|
691
|
+
`- Canonical 目标根:\`${payload.generationContext.canonicalTargetRoot}\``,
|
|
692
|
+
));
|
|
693
|
+
lines.splice(4, 0, localize(
|
|
694
|
+
`- Audit file: \`${payload.generationContext.auditRef}\``,
|
|
695
|
+
`- 审计文件:\`${payload.generationContext.auditRef}\``,
|
|
696
|
+
));
|
|
697
|
+
lines.push(localize(
|
|
698
|
+
`5. Inspect the declared source materials: code roots (${payload.generationContext.codeRoots.join(", ") || "none"}), docs roots (${payload.generationContext.docsRoots.join(", ") || "none"}), structure roots (${payload.generationContext.structureRoots.join(", ") || "none"}).`,
|
|
699
|
+
`5. 检查声明的源材料:代码根(${payload.generationContext.codeRoots.join(", ") || "none"})、文档根(${payload.generationContext.docsRoots.join(", ") || "none"})、结构根(${payload.generationContext.structureRoots.join(", ") || "none"})。`,
|
|
700
|
+
));
|
|
701
|
+
lines.push(localize(
|
|
702
|
+
`6. Build real canonical files first: ${payload.generationContext.requiredFileClasses.join(", ")}.`,
|
|
703
|
+
`6. 先生成真实 canonical 文件:${payload.generationContext.requiredFileClasses.join(", ")}。`,
|
|
704
|
+
));
|
|
705
|
+
if (payload.generationContext.minimumGenerationSequence.length > 0) {
|
|
706
|
+
lines.push(localize(
|
|
707
|
+
`6a. Start with this minimum skeleton: ${payload.generationContext.minimumGenerationSequence.join(", ")}.`,
|
|
708
|
+
`6a. 先从这组最小骨架开始:${payload.generationContext.minimumGenerationSequence.join(", ")}。`,
|
|
709
|
+
));
|
|
710
|
+
}
|
|
711
|
+
lines.push(localize(
|
|
712
|
+
`7. Write \`${payload.generationContext.auditRef}\` for every generated canonical file. Record source refs, source basis, and unresolved gaps explicitly.`,
|
|
713
|
+
`7. 为每个生成的 canonical 文件写入 \`${payload.generationContext.auditRef}\`。显式记录 source refs、source basis 和未解决缺口。`,
|
|
714
|
+
));
|
|
715
|
+
if (payload.generationContext.skeletonRules.length > 0) {
|
|
716
|
+
lines.push(localize(
|
|
717
|
+
`7a. Apply these skeleton rules: ${payload.generationContext.skeletonRules.join(", ")}.`,
|
|
718
|
+
`7a. 应用这些骨架规则:${payload.generationContext.skeletonRules.join(", ")}。`,
|
|
719
|
+
));
|
|
720
|
+
}
|
|
721
|
+
lines.push(localize(
|
|
722
|
+
"7b. If the first pass reaches a valid minimal skeleton but still contains explicit inferred or unresolved areas, keep the audit explicit and return a partial reconstruction result instead of claiming full completion.",
|
|
723
|
+
"7b. 如果第一轮只达到有效的最小骨架,但仍包含显式的 inferred 或 unresolved 区域,请保持审计显式,并返回 partial reconstruction 结果,而不是宣称完全完成。",
|
|
724
|
+
));
|
|
725
|
+
if (payload.generationContext.benchmarkBlueprintRoot) {
|
|
726
|
+
lines.push(localize(
|
|
727
|
+
`8. Use benchmark root \`${payload.generationContext.benchmarkBlueprintRoot}\` as an acceptance benchmark. Aim for semantic and structural parity; do not optimize for file-by-file copying.`,
|
|
728
|
+
`8. 将 benchmark root \`${payload.generationContext.benchmarkBlueprintRoot}\` 作为验收基准。目标是语义和结构等价;不要追求逐文件复制。`,
|
|
729
|
+
));
|
|
730
|
+
} else {
|
|
731
|
+
lines.push(localize(
|
|
732
|
+
"8. No benchmark blueprint is declared. Infer the domain set from the mixed inputs and keep the canonical tree explicit and fail-closed.",
|
|
733
|
+
"8. 当前没有声明 benchmark blueprint。请根据 mixed inputs 推断域集合,并保持 canonical tree 显式且 fail-close。",
|
|
734
|
+
));
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
if (hostId === "oh-my-codex") {
|
|
738
|
+
lines.push(localize(
|
|
739
|
+
"Additional OMX rule: keep `.omx/**` and external execution state operational only. Do not write semantic truth directly into `.nimi/spec/**`.",
|
|
740
|
+
"额外 OMX 规则:将 `.omx/**` 和外部执行状态保持为 operational only。不要直接把语义 truth 写入 `.nimi/spec/**`。",
|
|
741
|
+
));
|
|
742
|
+
}
|
|
743
|
+
if (hostId === "codex") {
|
|
744
|
+
lines.push(localize(
|
|
745
|
+
"Additional Codex rule: use the native Codex SDK boundary for execution. Do not route this through oh-my-codex or shell out to the Codex CLI.",
|
|
746
|
+
"额外 Codex 规则:执行时使用原生 Codex SDK 边界。不要经由 oh-my-codex,也不要 shell 到 Codex CLI。",
|
|
747
|
+
));
|
|
748
|
+
}
|
|
749
|
+
lines.push(localize(
|
|
750
|
+
`Host profile: ${host.label}.`,
|
|
751
|
+
`Host 配置:${host.zhLabel}。`,
|
|
752
|
+
));
|
|
753
|
+
return `${lines.join("\n")}\n`;
|
|
754
|
+
}
|
|
755
|
+
function toPortablePath(relativePath) {
|
|
756
|
+
return relativePath.split(path.sep).join("/");
|
|
757
|
+
}
|
|
758
|
+
function buildHandoffArtifactRefs(skillId) {
|
|
759
|
+
const safeSkillId = skillId.replace(/[^A-Za-z0-9._-]/g, "-");
|
|
760
|
+
return {
|
|
761
|
+
jsonRef: path.join(".nimi", "local", "handoff", `${safeSkillId}.json`),
|
|
762
|
+
promptRef: path.join(".nimi", "local", "handoff", `${safeSkillId}.prompt.md`),
|
|
763
|
+
};
|
|
764
|
+
}
|
|
765
|
+
export async function writeHandoffPromptArtifacts(projectRoot, payload) {
|
|
766
|
+
const refs = buildHandoffArtifactRefs(payload.skill.id);
|
|
767
|
+
const absoluteJsonPath = path.join(projectRoot, refs.jsonRef);
|
|
768
|
+
const absolutePromptPath = path.join(projectRoot, refs.promptRef);
|
|
769
|
+
await mkdir(path.dirname(absoluteJsonPath), { recursive: true });
|
|
770
|
+
await writeFile(absoluteJsonPath, `${JSON.stringify(payload, null, 2)}\n`, "utf8");
|
|
771
|
+
await writeFile(absolutePromptPath, formatHandoffPrompt(payload), "utf8");
|
|
772
|
+
return {
|
|
773
|
+
jsonRef: toPortablePath(refs.jsonRef),
|
|
774
|
+
promptRef: toPortablePath(refs.promptRef),
|
|
775
|
+
};
|
|
776
|
+
}
|
|
777
|
+
export async function writeHandoffJsonArtifact(projectRoot, payload) {
|
|
778
|
+
const refs = buildHandoffArtifactRefs(payload.skill.id);
|
|
779
|
+
const absoluteJsonPath = path.join(projectRoot, refs.jsonRef);
|
|
780
|
+
await mkdir(path.dirname(absoluteJsonPath), { recursive: true });
|
|
781
|
+
await writeFile(absoluteJsonPath, `${JSON.stringify(payload, null, 2)}\n`, "utf8");
|
|
782
|
+
return {
|
|
783
|
+
jsonRef: toPortablePath(refs.jsonRef),
|
|
784
|
+
};
|
|
785
|
+
}
|