create-yss-spec 3.1.0 → 3.1.3
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/README.md +117 -91
- package/bin/create-yss-spec.js +11 -3
- package/package.json +9 -2
- package/src/api/_sync-service.js +191 -0
- package/src/api/index.js +22 -0
- package/src/api/project-diff.js +9 -0
- package/src/api/project-doctor.js +10 -0
- package/src/api/template-apply.js +10 -0
- package/src/api/template-plan.js +9 -0
- package/src/cli/args.js +60 -0
- package/src/cli/error-output.js +52 -0
- package/src/cli/flags.js +9 -0
- package/src/cli/help.js +95 -0
- package/src/cli/index.js +82 -0
- package/src/cli/plan-output.js +61 -0
- package/src/cli/prompts.js +91 -0
- package/src/cli/router.js +41 -0
- package/src/cli.js +5 -2253
- package/src/commands/attach.js +280 -0
- package/src/commands/diff.js +23 -0
- package/src/commands/doctor.js +417 -0
- package/src/commands/init.js +193 -0
- package/src/commands/sync.js +146 -0
- package/src/commands/update.js +14 -0
- package/src/contracts/apply-result-v1.json +45 -0
- package/src/contracts/customization-policy-v1.json +28 -0
- package/src/contracts/doctor-report-v1.json +29 -0
- package/src/contracts/error-envelope-v1.json +24 -0
- package/src/contracts/generator-policy-v1.json +24 -0
- package/src/contracts/ownership-policy-v1.json +40 -0
- package/src/contracts/plan-schema-v1.json +115 -0
- package/src/family-identity.js +365 -0
- package/src/family-runtime.js +23 -0
- package/src/filesystem/apply-plan.js +44 -0
- package/src/filesystem/copy-path.js +28 -0
- package/src/filesystem/path-utils.js +67 -0
- package/src/filesystem/transaction-runner.js +41 -0
- package/src/filesystem/transaction.js +153 -0
- package/src/git/submodule.js +115 -0
- package/src/git/worktree.js +69 -0
- package/src/template/attach-planner-runtime.js +51 -0
- package/src/template/attach-planner.js +160 -0
- package/src/template/instance-runtime.js +463 -0
- package/src/template/lifecycle-metadata.js +99 -0
- package/src/template/lifecycle-policy.js +129 -0
- package/src/template/lifecycle-runtime.js +90 -0
- package/src/template/migration-planner.js +95 -0
- package/src/template/migration-runtime.js +247 -0
- package/src/template/ownership-metadata.js +79 -0
- package/src/template/ownership-policy.js +149 -0
- package/src/template/ownership-runtime.js +109 -0
- package/src/template/plan-schema.js +36 -0
- package/src/template/sync-planner-runtime.js +57 -0
- package/src/template/sync-planner.js +206 -0
- package/src/template/verification-runtime.js +154 -0
- package/src/validation/identity.js +41 -0
- package/src/validation/metadata.js +105 -0
- package/src/validation/security.js +67 -0
- package/src/validation/snapshot.js +47 -0
- package/template/.agents/skills/.strategic-design-skills-manifest.json +1 -1
- package/template/.agents/skills/yss-frontend-scaffold-generator/SKILL.md +2 -0
- package/template/.agents/skills/yss-implementation-contract-compiler/SKILL.md +2 -0
- package/template/.agents/skills/yss-implementation-contract-compiler/references/compiler-contract.yaml +6 -0
- package/template/.agents/skills/yss-implementation-contract-compiler/references/slice-implementation-contract.md +2 -0
- package/template/.agents/skills/yss-product-lifecycle/SKILL.md +2 -0
- package/template/.agents/skills/yss-ui/SKILL.md +2 -0
- package/template/.claude/skills/yss-frontend-scaffold-generator/SKILL.md +2 -0
- package/template/.claude/skills/yss-implementation-contract-compiler/SKILL.md +2 -0
- package/template/.claude/skills/yss-implementation-contract-compiler/references/compiler-contract.yaml +6 -0
- package/template/.claude/skills/yss-implementation-contract-compiler/references/slice-implementation-contract.md +2 -0
- package/template/.claude/skills/yss-product-lifecycle/SKILL.md +2 -0
- package/template/.claude/skills/yss-ui/SKILL.md +2 -0
- package/template/.codex/skills/yss-frontend-scaffold-generator/SKILL.md +2 -0
- package/template/.codex/skills/yss-implementation-contract-compiler/SKILL.md +2 -0
- package/template/.codex/skills/yss-implementation-contract-compiler/references/compiler-contract.yaml +6 -0
- package/template/.codex/skills/yss-implementation-contract-compiler/references/slice-implementation-contract.md +2 -0
- package/template/.codex/skills/yss-product-lifecycle/SKILL.md +2 -0
- package/template/.codex/skills/yss-ui/SKILL.md +2 -0
- package/template/.cursor/skills/yss-frontend-scaffold-generator/SKILL.md +2 -0
- package/template/.cursor/skills/yss-implementation-contract-compiler/SKILL.md +2 -0
- package/template/.cursor/skills/yss-implementation-contract-compiler/references/compiler-contract.yaml +6 -0
- package/template/.cursor/skills/yss-implementation-contract-compiler/references/slice-implementation-contract.md +2 -0
- package/template/.cursor/skills/yss-product-lifecycle/SKILL.md +2 -0
- package/template/.cursor/skills/yss-ui/SKILL.md +2 -0
- package/template/.pi/skills/yss-frontend-scaffold-generator/SKILL.md +2 -0
- package/template/.pi/skills/yss-implementation-contract-compiler/SKILL.md +2 -0
- package/template/.pi/skills/yss-implementation-contract-compiler/references/compiler-contract.yaml +6 -0
- package/template/.pi/skills/yss-implementation-contract-compiler/references/slice-implementation-contract.md +2 -0
- package/template/.pi/skills/yss-product-lifecycle/SKILL.md +2 -0
- package/template/.pi/skills/yss-ui/SKILL.md +2 -0
- package/template/.qoder/skills/yss-frontend-scaffold-generator/SKILL.md +2 -0
- package/template/.qoder/skills/yss-implementation-contract-compiler/SKILL.md +2 -0
- package/template/.qoder/skills/yss-implementation-contract-compiler/references/compiler-contract.yaml +6 -0
- package/template/.qoder/skills/yss-implementation-contract-compiler/references/slice-implementation-contract.md +2 -0
- package/template/.qoder/skills/yss-product-lifecycle/SKILL.md +2 -0
- package/template/.qoder/skills/yss-ui/SKILL.md +2 -0
- package/template/.trae/skills/yss-frontend-scaffold-generator/SKILL.md +2 -0
- package/template/.trae/skills/yss-implementation-contract-compiler/SKILL.md +2 -0
- package/template/.trae/skills/yss-implementation-contract-compiler/references/compiler-contract.yaml +6 -0
- package/template/.trae/skills/yss-implementation-contract-compiler/references/slice-implementation-contract.md +2 -0
- package/template/.trae/skills/yss-product-lifecycle/SKILL.md +2 -0
- package/template/.trae/skills/yss-ui/SKILL.md +2 -0
- package/template/CONTEXT.md +2 -0
- package/template/README.md +4 -0
- package/template/docs/agents/digital-human-roles.yaml +1 -0
- package/template/docs/process/frontend-backend-delivery.md +72 -0
- package/template/docs/process/implementation-repo-integration.md +2 -0
- package/template/docs/process/lifecycle-artifact-map.md +1 -0
- package/template/docs/process/lifecycle-registry-baseline.json +2 -1
- package/template/docs/process/lifecycle-registry.yaml +5 -0
- package/template/docs/process/schemas/backend-delivery-verification.schema.json +113 -0
- package/template/docs/process/schemas/backend-delivery.schema.json +301 -0
- package/template/docs/process/schemas/digital-human-task-package.schema.json +8 -0
- package/template/docs/process/schemas/frontend-delivery-acceptance.schema.json +200 -0
- package/template/docs/process/schemas/frontend-implementation-evidence.schema.json +3 -0
- package/template/docs/process/template-verification-profiles.yaml +6 -0
- package/template/docs/user-guide//346/210/230/346/234/257/350/256/276/350/256/241/345/255/220/351/241/271/347/233/256/347/224/250/346/210/267/346/211/213/345/206/214.md +20 -206
- package/template/docs/user-guide//347/224/250/346/210/267/346/211/213/345/206/214.md +63 -963
- package/template/docs/user-guide//347/224/250/346/210/267/346/211/213/345/206/214/347/264/242/345/274/225.md +11 -14
- package/template/docs/user-guide//350/256/276/345/244/207/345/200/237/347/224/250/350/264/257/347/251/277/346/241/210/344/276/213.md +127 -0
- package/template/scripts/backend-delivery +14 -0
- package/template/scripts/fixtures/backend-delivery/revision-server.mjs +11 -0
- package/template/scripts/instantiate-harness +69 -0
- package/template/scripts/lib/backend-delivery.mjs +164 -0
- package/template/scripts/lib/frontend-delivery-boundary.mjs +49 -0
- package/template/scripts/lib/frontend-delivery.mjs +73 -0
- package/template/scripts/lib/harness-execution-scope.mjs +52 -0
- package/template/scripts/lib/implementation-contract-compiler.mjs +12 -2
- package/template/scripts/lib/lifecycle-transition.mjs +7 -0
- package/template/scripts/lib/strategic-handoff-consumption.mjs +14 -7
- package/template/scripts/lib/strategic-handoff.mjs +1 -1
- package/template/scripts/lib/task-package.mjs +7 -0
- package/template/scripts/sync-strategic-handoff-tools +3 -1
- package/template/scripts/verify-frontend-delivery +9 -0
- package/template/scripts/verify-frontend-delivery-scenarios +131 -0
- package/template/scripts/verify-frontend-implementation-evidence +2 -0
- package/template/skills-lock.json +11 -11
- package/template.manifest.json +43 -1
- package/template.snapshot.json +5 -5
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { enforceHarnessSkillScope } from "./harness-execution-scope.mjs";
|
|
1
2
|
import { createHash } from "node:crypto";
|
|
2
3
|
import { readFileSync } from "node:fs";
|
|
3
4
|
import path from "node:path";
|
|
@@ -5,6 +6,7 @@ import { parseDocument } from "../vendor/yaml.mjs";
|
|
|
5
6
|
import { DEFAULT_REGISTRY, loadSkillRegistry } from "./skill-registry.mjs";
|
|
6
7
|
import { ROOT } from "./skill-supply-chain.mjs";
|
|
7
8
|
import { architectureDigest, assertArchitectureAgreement, validateArchitectureIdentity } from "./backend-architecture.mjs";
|
|
9
|
+
import { enforceFrontendDelivery } from "./frontend-delivery-boundary.mjs";
|
|
8
10
|
|
|
9
11
|
export const DEFAULT_COMPILER_CONTRACT = path.join(
|
|
10
12
|
ROOT,
|
|
@@ -61,9 +63,13 @@ export function compileImplementationContract({
|
|
|
61
63
|
registryDigest,
|
|
62
64
|
compilerContractDigest,
|
|
63
65
|
architecture_identity,
|
|
64
|
-
architecture_evidence
|
|
66
|
+
architecture_evidence,
|
|
67
|
+
root = ROOT,
|
|
68
|
+
slice_id,
|
|
69
|
+
frontend_delivery
|
|
65
70
|
}) {
|
|
66
71
|
assertV2(registry, compilerContract);
|
|
72
|
+
const deliveryInput = enforceFrontendDelivery({ slice_id, frontend_delivery }, { root });
|
|
67
73
|
if (!Array.isArray(recipeIds) || !Array.isArray(requiredCapabilities) || !Array.isArray(conditions)) {
|
|
68
74
|
fail("recipeIds、requiredCapabilities 与 conditions 必须是数组");
|
|
69
75
|
}
|
|
@@ -156,10 +162,12 @@ export function compileImplementationContract({
|
|
|
156
162
|
orderedSkills.push(skill);
|
|
157
163
|
};
|
|
158
164
|
for (const root of roots) visit(root);
|
|
165
|
+
enforceHarnessSkillScope(orderedSkills, registry, { root });
|
|
159
166
|
|
|
160
167
|
return {
|
|
161
168
|
schema_version: 2,
|
|
162
169
|
status: "draft",
|
|
170
|
+
...(deliveryInput.result === "inputs-verified" ? { slice_id: deliveryInput.slice_id, frontend_delivery: { acceptance_ref: deliveryInput.acceptance_ref, digest: deliveryInput.acceptance_digest } } : {}),
|
|
163
171
|
...(architecture_identity ? {
|
|
164
172
|
architecture_identity: structuredClone(architecture_identity),
|
|
165
173
|
architecture_identity_digest: architectureDigest(architecture_identity),
|
|
@@ -189,11 +197,13 @@ export function compileDefaultImplementationContract(input = {}) {
|
|
|
189
197
|
});
|
|
190
198
|
}
|
|
191
199
|
|
|
192
|
-
export function evaluateContractFreshness(contract, { registry, compilerContract }) {
|
|
200
|
+
export function evaluateContractFreshness(contract, { registry, compilerContract, root = ROOT }) {
|
|
193
201
|
assertV2(registry, compilerContract);
|
|
194
202
|
if (contract?.schema_version !== 2) fail("Slice Implementation Contract schema v1 已停止支持;必须重新编译 v2 合同");
|
|
195
203
|
const resolution = contract.resolution ?? contract;
|
|
196
204
|
const reasons = [];
|
|
205
|
+
try { enforceFrontendDelivery(contract, { root }); }
|
|
206
|
+
catch { reasons.push("frontend-delivery-stale-or-unavailable"); }
|
|
197
207
|
if (resolution.registry_digest !== digestDocument(registry)) reasons.push("registry-digest-changed");
|
|
198
208
|
if (resolution.compiler_contract_digest !== digestDocument(compilerContract)) reasons.push("compiler-contract-digest-changed");
|
|
199
209
|
if (resolution.architecture_identity) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
2
|
import { parseDocument } from "../vendor/yaml.mjs";
|
|
3
3
|
import { assertWorkUnitUserDecision, assertImplementationDecision } from "./user-decision.mjs";
|
|
4
|
+
import { enforceFrontendDelivery } from "./frontend-delivery-boundary.mjs";
|
|
4
5
|
|
|
5
6
|
const IMPLEMENTATION_WORK_UNIT = "work-unit.slice-implementation";
|
|
6
7
|
const TICKET_DECOMPOSITION_WORK_UNIT = "work-unit.ticket-decomposition";
|
|
@@ -112,6 +113,10 @@ function validateTicketReference(ref, trackerKind) {
|
|
|
112
113
|
* by `validateWorkflowExecutionResult` before this function is called.
|
|
113
114
|
*/
|
|
114
115
|
export function validateNextRoute(currentWorkUnit, nextRoute, decisionState, options = {}) {
|
|
116
|
+
if (["work-unit.technical-analysis", TICKET_DECOMPOSITION_WORK_UNIT, IMPLEMENTATION_WORK_UNIT, "work-unit.frontend-implementation-verification"].includes(nextRoute)) {
|
|
117
|
+
try { enforceFrontendDelivery(decisionState, { root: options.root, phase: nextRoute === IMPLEMENTATION_WORK_UNIT ? "implementation" : "inputs" }); }
|
|
118
|
+
catch (error) { return blockedResult(["frontend-delivery-blocked"], [error.message]); }
|
|
119
|
+
}
|
|
115
120
|
const routes = NEXT_ROUTES[currentWorkUnit];
|
|
116
121
|
if (!routes) return blockedResult([BLOCKING_SIGNALS.invalidRoute], ["known_current_work_unit"]);
|
|
117
122
|
if (nextRoute === null && routes.length === 0) {
|
|
@@ -138,6 +143,8 @@ function validateDecisionBoundary(workUnit, state, options) {
|
|
|
138
143
|
* `exists` is injectable so external adapters can resolve their own tracker refs.
|
|
139
144
|
*/
|
|
140
145
|
export function validateTicketFormalization(state, { exists = existsSync, read = (ref) => readFileSync(ref, "utf8"), ...decisionOptions } = {}) {
|
|
146
|
+
try { enforceFrontendDelivery(state, { root: decisionOptions.root, phase: "implementation" }); }
|
|
147
|
+
catch (error) { return blockedResult(["frontend-delivery-blocked"], [error.message]); }
|
|
141
148
|
const decomposition = state?.ticket_decomposition_result;
|
|
142
149
|
const ticket = state?.vertical_slice_ticket;
|
|
143
150
|
const contract = state?.slice_contract;
|
|
@@ -11,9 +11,10 @@ const strategyBasis=value=>({
|
|
|
11
11
|
...without(value,['domain_version','approval','rule_catalog','scenarios','invariants']),
|
|
12
12
|
invariants:(value.invariants||[]).map(item=>without(item,['statement'])),
|
|
13
13
|
});
|
|
14
|
-
export async function verifyConsumption(data,{root=process.cwd(),sliceRef}={}) {
|
|
14
|
+
export async function verifyConsumption(data,{root=process.cwd(),sliceRef,consumer='tactical'}={}) {
|
|
15
15
|
project(root);
|
|
16
|
-
|
|
16
|
+
ensure(['tactical','frontend'].includes(consumer),'未知战略消费者');
|
|
17
|
+
if (sliceRef) ensure(data.status === (consumer==='frontend'?'accepted':'approved'), consumer==='frontend'?'前端接收记录尚未 accepted':'切片只能消费 approved 战术合同');
|
|
17
18
|
const binding=data.strategic_handoff;
|
|
18
19
|
ensure(binding && present(binding.import_receipt_ref) && present(binding.context_reconciliation_ref),'缺少战略交接导入收据与目标对账引用');
|
|
19
20
|
const receipt=read(safe(root,binding.import_receipt_ref));
|
|
@@ -44,15 +45,21 @@ export async function verifyConsumption(data,{root=process.cwd(),sliceRef}={}) {
|
|
|
44
45
|
const catalogs=['aggregate_catalog','entity_catalog','value_object_catalog','behavior_catalog','invariant_catalog','state_transition_catalog','domain_event_catalog','gateway_catalog'];
|
|
45
46
|
const ids=new Set(catalogs.flatMap(k=>(data[k]||[]).flatMap(x=>Object.entries(x).filter(([k])=>k.endsWith('_id')).map(([,v])=>v))));
|
|
46
47
|
const seams=new Map((data.test_seams||[]).map(x=>[x.seam_id,x]));
|
|
48
|
+
const cases=new Map((data.frontend_cases||[]).map(x=>[x.case_id,x]));
|
|
47
49
|
for(const[id,source]of known){
|
|
48
50
|
const row=rows.get(id);
|
|
49
51
|
if(!row){mark(id,'missing-mapping');continue;}
|
|
50
52
|
ensure(['known','unknown'].includes(row.dependency_status) && Array.isArray(row.dependent_slice_refs),'缺少切片依赖判定');
|
|
51
53
|
if(row.source_digest!==source.source_digest){mark(id,'stale-source',row);continue;}
|
|
52
|
-
if(row.disposition==='implemented'){
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
if(row.disposition===(consumer==='frontend'?'mapped':'implemented')){
|
|
55
|
+
if(consumer==='frontend'){
|
|
56
|
+
ensure(list(row.frontend_case_refs)&&row.frontend_case_refs.every(ref=>cases.has(ref)&&cases.get(ref).source_ids.includes(id)),`前端用例未承接源规则/场景: ${id}`);
|
|
57
|
+
if(id.startsWith('scenario.'))ensure(['success','failure'].every(outcome=>row.frontend_case_refs.some(ref=>cases.get(ref).outcome===outcome)),`前端关键场景缺少成功/失败用例: ${id}`);
|
|
58
|
+
}else{
|
|
59
|
+
ensure(list(row.tactical_refs)&&row.tactical_refs.every(x=>ids.has(x)),`战术落点悬空: ${id}`);
|
|
60
|
+
ensure(list(row.test_seam_refs)&&row.test_seam_refs.every(x=>seams.has(x)&&row.tactical_refs.includes(seams.get(x).subject_ref)),`测试 seam 未关联战术落点: ${id}`);
|
|
61
|
+
if(id.startsWith('scenario.'))ensure(['success','failure'].every(outcome=>(row.scenario_tests||[]).some(x=>x.outcome===outcome&&row.test_seam_refs.includes(x.seam_ref))),`关键场景缺少成功/失败测试: ${id}`);
|
|
62
|
+
}
|
|
56
63
|
ensure(list(row.evidence_refs),`缺少落实证据: ${id}`);row.evidence_refs.forEach(ref=>safe(root,ref));
|
|
57
64
|
}else if(row.disposition==='deferred'){
|
|
58
65
|
for(const key of ['reason','risk','owner','followup_ticket_ref','verification_plan','target_version'])ensure(present(row[key]),`延期缺少 ${key}: ${id}`);
|
|
@@ -70,7 +77,7 @@ export async function verifyConsumption(data,{root=process.cwd(),sliceRef}={}) {
|
|
|
70
77
|
all=true;issues.push({source_id:'strategic-design-basis',reason:'upstream-design-stale'});
|
|
71
78
|
}
|
|
72
79
|
const blockedHere=all||(sliceRef?blocked.has(sliceRef):issues.length>0);
|
|
73
|
-
return {result:blockedHere?'blocked':'verified',bundle_digest:current.manifest.bundle_digest,consumed_bundle_digest:receipt.bundle_digest
|
|
80
|
+
return {result:blockedHere?'blocked':'verified',bundle_digest:current.manifest.bundle_digest,consumed_bundle_digest:receipt.bundle_digest,...(consumer==='frontend'?{frontend_acceptance_digest:digest(data)}:{tactical_digest:digest(data)}),scope:sliceRef||`whole-${consumer}-design`,coverage_complete:[...known.keys()].every(id=>rows.has(id)),block_all:all,blocked_slice_refs:[...blocked].sort(),issues};
|
|
74
81
|
});
|
|
75
82
|
});
|
|
76
83
|
}
|
|
@@ -13,7 +13,7 @@ const own = (a,b) => JSON.stringify(canonical(a)) === JSON.stringify(canonical(b
|
|
|
13
13
|
const nonempty = x => typeof x === 'string' && x.trim();
|
|
14
14
|
const bytesDigest = (bytes, kind) => kind === 'canonical-json' ? digest(parse(bytes)) : kind === 'sha256-bytes' ? hash(bytes) : (() => { throw new TypeError(`未知摘要算法: ${kind}`); })();
|
|
15
15
|
|
|
16
|
-
async function sourceApproval(record, roles, root) {
|
|
16
|
+
export async function sourceApproval(record, roles, root) {
|
|
17
17
|
const options={rolesDoc:roles,requireApproved:true,root,read:ref=>readFileSync(safe(root,path.relative(root,ref).split(path.sep).join('/')))};
|
|
18
18
|
if (roles.user_decision_policy.gates.includes(record.gate_id)) {
|
|
19
19
|
ensure(existsSync(path.join(ROOT,'scripts/lib/user-decision.mjs')), '接收工具不支持源用户决定策略,请升级工具后验包');
|
|
@@ -6,6 +6,8 @@ import { loadRegistry, ROOT } from "./lifecycle-registry.mjs";
|
|
|
6
6
|
import { loadMaintenanceCheckpoint, validateMaintenanceCheckpoint } from "./maintenance-intensity.mjs";
|
|
7
7
|
import { assertImplementationDecision } from "./user-decision.mjs";
|
|
8
8
|
import { validateNextRoute } from "./lifecycle-transition.mjs";
|
|
9
|
+
import { enforceHarnessTaskScope } from "./harness-execution-scope.mjs";
|
|
10
|
+
import { enforceFrontendDelivery } from "./frontend-delivery-boundary.mjs";
|
|
9
11
|
import { LEGACY_TASK_PACKAGE_SCHEMA, TASK_PACKAGE_SCHEMA, validateTaskPackageSchema } from "./task-package-schema.mjs";
|
|
10
12
|
|
|
11
13
|
export { LEGACY_TASK_PACKAGE_SCHEMA, TASK_PACKAGE_SCHEMA, validateTaskPackageSchema };
|
|
@@ -181,6 +183,11 @@ function validateContract(value, registry, lifecycle) {
|
|
|
181
183
|
|
|
182
184
|
export function validateTaskPackage(value, { rolesDoc, lifecycleDoc } = {}) {
|
|
183
185
|
validateTaskPackageSchema(value);
|
|
186
|
+
enforceHarnessTaskScope(value);
|
|
187
|
+
const intake = value.execution_state === "Explorer" && value.allowed_write_paths.length === 0 && ["work-unit.entry-triage", "work-unit.harness-entry"].includes(value.work_unit_id);
|
|
188
|
+
if (!intake) {
|
|
189
|
+
enforceFrontendDelivery(value, { phase: value.contract.kind === "slice-implementation" ? "implementation" : "inputs" });
|
|
190
|
+
}
|
|
184
191
|
const registry = rolesDoc || loadDigitalHumanRoles();
|
|
185
192
|
const lifecycle = lifecycleDoc || loadRegistry();
|
|
186
193
|
validateCommon(value, registry, lifecycle);
|
|
@@ -5,14 +5,16 @@ import path from 'node:path';
|
|
|
5
5
|
import { inspectWorkingTreeScope } from './lib/repository-scope-policy.mjs';
|
|
6
6
|
const root=path.resolve(import.meta.dirname,'..');
|
|
7
7
|
const check=process.argv.includes('--check');
|
|
8
|
-
const targets=['submodules/yss-harness-design-agent','submodules/yss-harness-dev-agent'];
|
|
8
|
+
const targets=['submodules/yss-harness-design-agent','submodules/yss-harness-dev-agent','submodules/yss-harness-backend-agent','submodules/yss-harness-frontend-agent'];
|
|
9
9
|
const shared=['scripts/strategic-handoff','scripts/verify-strategic-handoff-consumption','scripts/verify-strategic-handoff-package-scenarios','scripts/verify-strategic-context-import','scripts/verify-strategic-context-import-scenarios','scripts/lib/context-contract.mjs','docs/process/strategic-handoff-package.md','docs/process/schemas/strategic-design-handoff.schema.json',...readdirSync(path.join(root,'scripts/lib')).filter(x=>x.startsWith('strategic-handoff-')||x==='strategic-handoff.mjs').map(x=>`scripts/lib/${x}`),...readdirSync(path.join(root,'docs/process/schemas')).filter(x=>x.startsWith('strategic-handoff-')).map(x=>`docs/process/schemas/${x}`),...readdirSync(path.join(root,'scripts/fixtures/strategic-handoff')).map(x=>`scripts/fixtures/strategic-handoff/${x}`)];
|
|
10
|
+
shared.push('scripts/instantiate-harness','scripts/lib/harness-execution-scope.mjs','scripts/backend-delivery','scripts/verify-frontend-delivery','scripts/lib/backend-delivery.mjs','scripts/lib/frontend-delivery.mjs','scripts/lib/frontend-delivery-boundary.mjs','docs/process/frontend-backend-delivery.md','docs/process/schemas/backend-delivery.schema.json','docs/process/schemas/backend-delivery-verification.schema.json','docs/process/schemas/frontend-delivery-acceptance.schema.json');
|
|
10
11
|
let differences=[];
|
|
11
12
|
function sync(source,dest){const bytes=readFileSync(source);if(!existsSync(dest)||!bytes.equals(readFileSync(dest))){differences.push(path.relative(root,dest));if(!check){mkdirSync(path.dirname(dest),{recursive:true});writeFileSync(dest,bytes);}}if(!check&&path.basename(path.dirname(dest))==='scripts'&&!path.extname(dest))chmodSync(dest,0o755);}
|
|
12
13
|
for(const kind of ['domain-strategy','stage-decision-package'])sync(path.join(root,`.agents/skills/yss-stage-decision/references/${kind}.schema.json`),path.join(root,`docs/process/schemas/strategic-handoff-${kind}.schema.json`));
|
|
13
14
|
for(const target of targets){
|
|
14
15
|
if(!existsSync(path.join(root,target,'.git')))continue;
|
|
15
16
|
if(!check){const state=inspectWorkingTreeScope(root,{project_root:target,repository_scope:'git-submodule',checkout_state:'attached-branch'});if(!state.writable)throw new Error(`${target}: ${state.violation}`);}
|
|
17
|
+
if(!target.endsWith('design-agent'))for(const ref of ['scripts/verify-frontend-delivery-scenarios','scripts/fixtures/backend-delivery/revision-server.mjs'])sync(path.join(root,ref),path.join(root,target,ref));
|
|
16
18
|
for(const ref of new Set(shared))sync(path.join(root,ref),path.join(root,target,ref));
|
|
17
19
|
}
|
|
18
20
|
if(check&&differences.length){process.stderr.write(`交接工具同步漂移: ${differences.join(', ')}\n`);process.exitCode=1;}else process.stdout.write(`战略交接共享工具 ${check?'同步检查通过':`同步 ${differences.length} 文件`}\n`);
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { parseArgs } from 'node:util';
|
|
3
|
+
import { verifyFrontendDelivery } from './lib/frontend-delivery.mjs';
|
|
4
|
+
try {
|
|
5
|
+
const {values,positionals}=parseArgs({allowPositionals:true,options:{root:{type:'string',default:process.cwd()},slice:{type:'string'},'expected-digest':{type:'string'}}});
|
|
6
|
+
if(positionals.length!==1||!values.slice)throw new TypeError('用法: verify-frontend-delivery --root <repo> --slice <slice-id> [--expected-digest <sha256>] <acceptance-ref>');
|
|
7
|
+
const result=await verifyFrontendDelivery({root:values.root,sliceRef:values.slice,expectedDigest:values['expected-digest'],acceptanceRef:positionals[0]});
|
|
8
|
+
process.stdout.write(`${JSON.stringify(result,null,2)}\n`);
|
|
9
|
+
} catch(error) { process.stderr.write(`${JSON.stringify({result:'blocked',error:error.message})}\n`);process.exitCode=1; }
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { test } from 'node:test';
|
|
3
|
+
import assert from 'node:assert/strict';
|
|
4
|
+
import { fork } from 'node:child_process';
|
|
5
|
+
import { once } from 'node:events';
|
|
6
|
+
import { mkdtempSync, mkdirSync, writeFileSync, readFileSync, rmSync, cpSync, existsSync } from 'node:fs';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
import { pathToFileURL } from 'node:url';
|
|
9
|
+
import { tmpdir } from 'node:os';
|
|
10
|
+
import { fixture, context } from './fixtures/strategic-handoff/fixture.mjs';
|
|
11
|
+
import { exportBundle } from './lib/strategic-handoff.mjs';
|
|
12
|
+
const backendTools=process.env.YSS_DELIVERY_TEST_BACKEND_ROOT;
|
|
13
|
+
const frontendTools=process.env.YSS_DELIVERY_TEST_FRONTEND_ROOT;
|
|
14
|
+
const { backendDeliveryBasis, exportBackendDelivery, openBackendDelivery, inspectBackendDelivery } = await import(backendTools?pathToFileURL(path.join(backendTools,'scripts/lib/backend-delivery.mjs')):'./lib/backend-delivery.mjs');
|
|
15
|
+
const {importBackendDelivery}=await import(frontendTools?pathToFileURL(path.join(frontendTools,'scripts/lib/backend-delivery.mjs')):'./lib/backend-delivery.mjs');
|
|
16
|
+
const { verifyFrontendDelivery } = await import(frontendTools?pathToFileURL(path.join(frontendTools,'scripts/lib/frontend-delivery.mjs')):'./lib/frontend-delivery.mjs');
|
|
17
|
+
const { enforceFrontendDelivery } = await import(frontendTools?pathToFileURL(path.join(frontendTools,'scripts/lib/frontend-delivery-boundary.mjs')):'./lib/frontend-delivery-boundary.mjs');
|
|
18
|
+
const { compileDefaultImplementationContract } = await import(frontendTools?pathToFileURL(path.join(frontendTools,'scripts/lib/implementation-contract-compiler.mjs')):'./lib/implementation-contract-compiler.mjs');
|
|
19
|
+
const { validateNextRoute, validateImplementationEntry } = await import(frontendTools?pathToFileURL(path.join(frontendTools,'scripts/lib/lifecycle-transition.mjs')):'./lib/lifecycle-transition.mjs');
|
|
20
|
+
import { read, hash, json, ROOT } from './lib/strategic-handoff-io.mjs';
|
|
21
|
+
const runtimeRegistry=read(path.join(frontendTools||ROOT,'docs/process/lifecycle-registry.yaml'));
|
|
22
|
+
const frontendStage=runtimeRegistry.stages.find(x=>x.id==='stage.frontend-engineering-design')?.id||runtimeRegistry.stages.find(x=>x.id==='stage.tactical-design')?.id||'stage.system-data-engineering';
|
|
23
|
+
const frontendWorkUnit=frontendStage==='stage.frontend-engineering-design'?'work-unit.frontend-engineering-design':frontendStage==='stage.tactical-design'?'work-unit.tactical-design':'work-unit.technical-analysis';
|
|
24
|
+
|
|
25
|
+
test('战略与后端交付联合接收:便携、真实服务、拒绝不完整/过期输入及执行入口绕过',async t=>{
|
|
26
|
+
const base=mkdtempSync(path.join(tmpdir(),'frontend-delivery-test-'));
|
|
27
|
+
const put=(root,ref,value)=>{mkdirSync(path.dirname(path.join(root,ref)),{recursive:true});writeFileSync(path.join(root,ref),typeof value==='string'?value:json(value));};
|
|
28
|
+
const source=path.join(base,'strategy'), backend=path.join(base,'backend'), frontend=path.join(base,'frontend');
|
|
29
|
+
[source,backend,frontend].forEach(root=>mkdirSync(root));
|
|
30
|
+
if(backendTools)cpSync(backendTools,backend,{recursive:true});
|
|
31
|
+
if(frontendTools)cpSync(frontendTools,frontend,{recursive:true});
|
|
32
|
+
const f=await fixture(source);
|
|
33
|
+
for(const root of [backend,frontend]) {put(root,'yss-project.yaml','schema_version: 1\nrepository_mode: project-instance\n');put(root,'CONTEXT.md',context);}
|
|
34
|
+
const strategic=await exportBundle({sourceRoot:source,handoffRef:'handoff.yaml',output:path.join(backend,'strategy-package')});
|
|
35
|
+
const roles=read(path.join(source,'docs/agents/digital-human-roles.yaml'));
|
|
36
|
+
for(const gate of ['gate.openapi-frozen','gate.slice-contract-approved']) {
|
|
37
|
+
roles.gate_policy.biological_human=(roles.gate_policy.biological_human||[]).filter(x=>x!==gate);
|
|
38
|
+
roles.gate_policy.digital_human_review=(roles.gate_policy.digital_human_review||[]).filter(x=>x.gate!==gate);
|
|
39
|
+
roles.gate_policy.digital_human_review.push({gate,countersigners:['role.product-manager']});
|
|
40
|
+
}
|
|
41
|
+
put(backend,'docs/agents/digital-human-roles.yaml',roles);
|
|
42
|
+
put(backend,'api.yaml',{openapi:'3.1.0',info:{title:'Synthetic supplier API',version:'1'},paths:{'/suppliers':{post:{operationId:'submitSupplier',responses:{'200':{description:'OK'}}}}}});
|
|
43
|
+
put(backend,'slice.json',{schema_version:2,contract_id:'contract.backend',contract_version:'v1',slice_id:'slice.submit',status:'approved'});
|
|
44
|
+
put(backend,'data.md','Synthetic data setup for maintenance tests, not product verification.');
|
|
45
|
+
put(backend,'verification.log','Synthetic test fixture: successful/failed submissions exercised.');
|
|
46
|
+
const file=ref=>({ref,digest:hash(readFileSync(path.join(backend,ref)))});
|
|
47
|
+
const approved=(ref,id,gate)=>{
|
|
48
|
+
const binding={...file(ref),id,version:'v1',approval_ref:`approvals/${id}.json`};
|
|
49
|
+
put(backend,binding.approval_ref,{schema_version:1,gate_id:gate,decision:'approved',actor_kind:'digital-human',role_id:'role.product-manager',runtime_id:'runtime.generic',principal_ref:'synthetic-maintenance-fixture',artifact_bindings:[{id,version:'v1',digest:binding.digest}]});return binding;
|
|
50
|
+
};
|
|
51
|
+
const serverState=path.join(base,'server.json');put(base,'server.json',{body:{}});
|
|
52
|
+
const server=fork(path.join(ROOT,'scripts/fixtures/backend-delivery/revision-server.mjs'),[serverState],{stdio:['ignore','ignore','inherit','ipc']});
|
|
53
|
+
t.after(()=>{server.kill();rmSync(base,{recursive:true,force:true});});
|
|
54
|
+
const [{port}]=await once(server,'message');
|
|
55
|
+
const delivery={schema_version:1,delivery_id:'backend-delivery.supplier',version:'v1',status:'verified',strategic_bundle_ref:'strategy-package',strategic_bundle_digest:strategic.bundle_digest,scope:{slice_id:'slice.submit',source_ids:['rule.complete','scenario.submit'],operation_ids:['submitSupplier']},openapi:approved('api.yaml','api.supplier','gate.openapi-frozen'),slice_contract:approved('slice.json','contract.backend','gate.slice-contract-approved'),build:{source_commit:'a'.repeat(40),artifact_digest:`sha256:${'b'.repeat(64)}`},environment:{id:'fixture-local',base_url:`http://127.0.0.1:${port}`,deployment_id:'fixture-v1',revision_path:'/version',revision_pointers:{deployment_id:'/deployment_id',source_commit:'/source_commit',openapi_digest:'/openapi_digest',artifact_digest:'/artifact_digest',test_data_digest:'/test_data_digest'},test_data:file('data.md')},verification:{},supporting_files:[]};
|
|
56
|
+
const record=kind=>({schema_version:1,kind,subject_digest:backendDeliveryBasis(delivery),results:[{command:'synthetic-maintenance-test',executed_at:new Date().toISOString(),exit_code:0,evidence:[file('verification.log')]}],operation_ids:['submitSupplier'],coverage:[{source_id:'scenario.submit',outcome:'success'},{source_id:'scenario.submit',outcome:'failure'}]});
|
|
57
|
+
put(backend,'contract-test.json',record('backend-contract'));put(backend,'deployment-test.json',record('backend-deployment'));
|
|
58
|
+
delivery.verification={contract:file('contract-test.json'),deployment:file('deployment-test.json')};put(backend,'delivery.json',delivery);
|
|
59
|
+
const body={deployment_id:'fixture-v1',source_commit:delivery.build.source_commit,openapi_digest:delivery.openapi.digest,artifact_digest:delivery.build.artifact_digest,test_data_digest:delivery.environment.test_data.digest};
|
|
60
|
+
put(base,'server.json',{body});
|
|
61
|
+
await t.test('导出拒绝缺失场景、篡改接口及错误批准',async()=>{
|
|
62
|
+
const bytes=readFileSync(path.join(backend,'api.yaml'));put(backend,'api.yaml','changed');
|
|
63
|
+
await assert.rejects(()=>inspectBackendDelivery(backend,'delivery.json'),/摘要不一致/);writeFileSync(path.join(backend,'api.yaml'),bytes);
|
|
64
|
+
const approval=read(path.join(backend,delivery.openapi.approval_ref));put(backend,delivery.openapi.approval_ref,{...approval,artifact_bindings:[]});
|
|
65
|
+
await assert.rejects(()=>inspectBackendDelivery(backend,'delivery.json'),/批准未绑定/);put(backend,delivery.openapi.approval_ref,approval);
|
|
66
|
+
const tests=record('backend-contract');tests.coverage.pop();put(backend,'contract-test.json',tests);
|
|
67
|
+
const broken=structuredClone(delivery);broken.verification.contract=file('contract-test.json');put(backend,'delivery.json',broken);
|
|
68
|
+
await assert.rejects(()=>inspectBackendDelivery(backend,'delivery.json'),/成功\/失败/);
|
|
69
|
+
put(backend,'contract-test.json',record('backend-contract'));delivery.verification.contract=file('contract-test.json');put(backend,'delivery.json',delivery);
|
|
70
|
+
});
|
|
71
|
+
const output=path.join(base,'package');await exportBackendDelivery({sourceRoot:backend,deliveryRef:'delivery.json',output,zip:true});
|
|
72
|
+
const imported=await importBackendDelivery({bundle:`${output}.zip`,targetRoot:frontend});
|
|
73
|
+
assert.equal((await importBackendDelivery({bundle:output,targetRoot:frontend})).result,'already-imported');
|
|
74
|
+
const acceptance=read(path.join(frontend,imported.acceptance_ref));
|
|
75
|
+
await t.test('草案/缺少两类产物不能进入前端任务',async()=>{
|
|
76
|
+
await assert.rejects(()=>verifyFrontendDelivery({root:frontend,acceptanceRef:imported.acceptance_ref,sliceRef:'slice.submit'}),/schema/);
|
|
77
|
+
const profileRef=path.join(frontend,'docs/process/harness-profile.yaml');
|
|
78
|
+
const profile=existsSync(profileRef)?read(profileRef):{schema_version:1,profile_id:'harness.frontend-delivery'};
|
|
79
|
+
put(frontend,'docs/process/harness-profile.yaml',{...profile,frontend_delivery:{required:true}});
|
|
80
|
+
assert.throws(()=>enforceFrontendDelivery({}, {root:frontend}),/frontend-delivery-required/);
|
|
81
|
+
assert.throws(()=>compileDefaultImplementationContract({root:frontend}),/frontend-delivery-required/);
|
|
82
|
+
assert.throws(()=>enforceFrontendDelivery({contract:{kind:'template-maintenance'}},{root:frontend}),/不得用模板维护/);
|
|
83
|
+
assert.equal(validateNextRoute(frontendWorkUnit==='work-unit.technical-analysis'?'work-unit.spec-synthesis':'work-unit.harness-entry',frontendWorkUnit,undefined,{root:frontend}).result,'blocked');
|
|
84
|
+
assert.equal(validateImplementationEntry({}, {root:frontend}).blocking_signals[0],'frontend-delivery-blocked');
|
|
85
|
+
});
|
|
86
|
+
put(frontend,'reconciliation.json',{schema_version:1,repository_mode:'project-instance',stage:frontendStage,work_unit:frontendWorkUnit,status:'reconciled',context_snapshot:f.snapshot,changes:{added:['Global/Supplier'],updated:[],deprecated:[]},unresolved_terms:[],evidence_refs:['CONTEXT.md']});
|
|
87
|
+
put(frontend,'cases.md','Synthetic planned UI cases: complete submission succeeds; incomplete submission fails.');
|
|
88
|
+
acceptance.status='accepted';acceptance.strategic_handoff.context_reconciliation_ref='reconciliation.json';
|
|
89
|
+
acceptance.frontend_cases=['success','failure'].map(outcome=>({case_id:`submit-${outcome}`,source_ids:['rule.complete','scenario.submit'],outcome,operation_ids:['submitSupplier'],visual_case_ids:['primary-desktop','primary-narrow'],evidence_ref:'cases.md',evidence_digest:hash(readFileSync(path.join(frontend,'cases.md')))}));
|
|
90
|
+
for(const row of acceptance.strategic_handoff.rows)Object.assign(row,{disposition:'mapped',frontend_case_refs:acceptance.frontend_cases.map(x=>x.case_id),dependency_status:'known',dependent_slice_refs:['slice.submit'],evidence_refs:['cases.md']});
|
|
91
|
+
put(frontend,imported.acceptance_ref,acceptance);
|
|
92
|
+
const check=()=>verifyFrontendDelivery({root:frontend,acceptanceRef:imported.acceptance_ref,sliceRef:'slice.submit'});
|
|
93
|
+
await t.test('源仓消失后可验 ZIP 并独立恢复;输入就绪不授予代码实施',async()=>{
|
|
94
|
+
rmSync(source,{recursive:true});rmSync(backend,{recursive:true});
|
|
95
|
+
assert.equal(await openBackendDelivery(`${output}.zip`,b=>b.delivery.scope.slice_id),'slice.submit');
|
|
96
|
+
const verified=await check();assert.equal(verified.result,'inputs-verified');assert.equal(verified.ready_for_agent,false);
|
|
97
|
+
assert.equal(verified.actual_verification.observed.deployment_id,'fixture-v1');
|
|
98
|
+
const draft=compileDefaultImplementationContract({root:frontend,slice_id:'slice.submit',frontend_delivery:{acceptance_ref:imported.acceptance_ref},requiredCapabilities:['governance.implementation-contract']});
|
|
99
|
+
assert.equal(draft.status,'draft');assert.equal(draft.slice_id,'slice.submit');assert.equal(draft.frontend_delivery.digest,verified.acceptance_digest);
|
|
100
|
+
assert.equal(enforceFrontendDelivery({slice_id:'slice.submit',frontend_delivery:{acceptance_ref:imported.acceptance_ref}}, {root:frontend}).result,'inputs-verified');
|
|
101
|
+
assert.throws(()=>enforceFrontendDelivery({slice_id:'slice.submit',frontend_delivery:{acceptance_ref:imported.acceptance_ref}}, {root:frontend,phase:'implementation'}),/approved Slice Contract/);
|
|
102
|
+
});
|
|
103
|
+
await t.test('实现边界读取持久化合同并冻结接收记录及用例字节',async()=>{
|
|
104
|
+
const binding={acceptance_ref:imported.acceptance_ref,digest:hash(readFileSync(path.join(frontend,imported.acceptance_ref)))};
|
|
105
|
+
put(frontend,'frontend-contract.json',{status:'approved',slice_id:'slice.submit',frontend:{delivery:binding}});
|
|
106
|
+
assert.equal(enforceFrontendDelivery({slice_contract_ref:'frontend-contract.json'},{root:frontend,phase:'implementation'}).result,'inputs-verified');
|
|
107
|
+
put(frontend,'cases.md','changed case');await assert.rejects(check,/摘要漂移/);
|
|
108
|
+
put(frontend,'cases.md','Synthetic planned UI cases: complete submission succeeds; incomplete submission fails.');
|
|
109
|
+
put(frontend,'frontend-contract.json',{status:'draft',slice_id:'slice.submit',frontend:{delivery:binding}});
|
|
110
|
+
assert.throws(()=>enforceFrontendDelivery({slice_contract_ref:'frontend-contract.json'},{root:frontend,phase:'implementation'}),/approved Slice Contract/);
|
|
111
|
+
});
|
|
112
|
+
await t.test('部署版本/数据失配与服务失败使旧接收记录失效',async()=>{
|
|
113
|
+
for(const field of ['deployment_id','source_commit','openapi_digest','artifact_digest','test_data_digest']) {
|
|
114
|
+
put(base,'server.json',{body:{...body,[field]:'wrong'}});await assert.rejects(check,/真实服务版本不匹配/);
|
|
115
|
+
}
|
|
116
|
+
put(base,'server.json',{status:503,body});await assert.rejects(check,/真实接口不可用/);put(base,'server.json',{body});
|
|
117
|
+
});
|
|
118
|
+
await t.test('范围、摘要和逐条承接缺口均拒绝',async()=>{
|
|
119
|
+
await assert.rejects(()=>verifyFrontendDelivery({root:frontend,acceptanceRef:imported.acceptance_ref,sliceRef:'slice.other'}),/切片不匹配/);
|
|
120
|
+
await assert.rejects(()=>verifyFrontendDelivery({root:frontend,acceptanceRef:imported.acceptance_ref,sliceRef:'slice.submit',expectedDigest:`sha256:${'0'.repeat(64)}`}),/摘要已变化/);
|
|
121
|
+
for(const mutate of [a=>a.frontend_cases.pop(),a=>a.strategic_handoff.rows[0].disposition='pending',a=>a.frontend_cases[0].operation_ids=['unknown'],a=>a.strategic_handoff.bundle_digest=`sha256:${'0'.repeat(64)}`]) {
|
|
122
|
+
const changed=structuredClone(acceptance);mutate(changed);put(frontend,imported.acceptance_ref,changed);await assert.rejects(check);
|
|
123
|
+
}
|
|
124
|
+
put(frontend,imported.acceptance_ref,acceptance);
|
|
125
|
+
});
|
|
126
|
+
await t.test('已导入更新版本阻断旧切片;包篡改拒绝',async()=>{
|
|
127
|
+
const receipt=read(path.join(frontend,imported.receipt_ref));const newer=path.posix.dirname(path.posix.dirname(imported.receipt_ref))+'/v2';
|
|
128
|
+
put(frontend,`${newer}/import-receipt.json`,{...receipt,version:'v2'});await assert.rejects(check,/旧前端接收记录 stale/);rmSync(path.join(frontend,newer),{recursive:true});
|
|
129
|
+
const changed=path.join(base,'tampered');cpSync(output,changed,{recursive:true});put(changed,'extra.txt','extra');await assert.rejects(()=>openBackendDelivery(changed,()=>null),/未登记文件/);
|
|
130
|
+
});
|
|
131
|
+
});
|
|
@@ -6,6 +6,7 @@ import { parseArgs } from "node:util";
|
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
7
|
import { parseDocument } from "./vendor/yaml.mjs";
|
|
8
8
|
import { validateVisualBaseline } from "../.agents/skills/yss-prototype-stage/scripts/visual-baseline-contract.mjs";
|
|
9
|
+
import { enforceFrontendDelivery } from "./lib/frontend-delivery-boundary.mjs";
|
|
9
10
|
|
|
10
11
|
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
11
12
|
const schemaPath = path.join(repoRoot, "docs/process/schemas/frontend-implementation-evidence.schema.json");
|
|
@@ -29,6 +30,7 @@ try {
|
|
|
29
30
|
const document = parseDocument(readFileSync(path.resolve(positionals[0]), "utf8"), { maxAliasCount: 0, uniqueKeys: true });
|
|
30
31
|
if (document.errors.length) throw new TypeError(document.errors[0].message);
|
|
31
32
|
const value = document.toJS({ maxAliasCount: 0 });
|
|
33
|
+
if (!values["allow-template"]) enforceFrontendDelivery(value, { root, phase: value.kind === "verification" ? "implementation" : "inputs" });
|
|
32
34
|
if (value.schema_version === 1 && !values["allow-template"]) throw new TypeError("migration-required: frontend implementation evidence v1 是只读历史资产;在途 UI 工作必须升级 v2 并绑定 Visual Baseline case_id");
|
|
33
35
|
const result = spawnSync("python3", ["-c", validator, schemaPath], { cwd: repoRoot, encoding: "utf8", input: JSON.stringify(value) });
|
|
34
36
|
if (result.status !== 0) throw new TypeError(`${result.stdout}${result.stderr}`.trim());
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"revision": "c4d958f18cc947cb91cbe1bd48783fa4601cc18f"
|
|
22
22
|
},
|
|
23
23
|
"iloveZzz/yss-harness-design-agent": {
|
|
24
|
-
"revision": "
|
|
24
|
+
"revision": "5c1c98225bb181c7c1add8972c1e44b866205f76"
|
|
25
25
|
}
|
|
26
26
|
},
|
|
27
27
|
"skills": {
|
|
@@ -559,7 +559,7 @@
|
|
|
559
559
|
"sourceType": "git-submodule",
|
|
560
560
|
"skillPath": ".agents/skills/prototype-review/SKILL.md",
|
|
561
561
|
"effectiveHash": "51021f119697dbff644d70c3d6ba3314147c47f412c6f56e4b5540ea3bf554a1",
|
|
562
|
-
"sourceRevision": "
|
|
562
|
+
"sourceRevision": "5c1c98225bb181c7c1add8972c1e44b866205f76",
|
|
563
563
|
"upstreamHash": "2e77b3093a758bb692fb3bda453c5c020d0523f875975e17e2731365f67432e1",
|
|
564
564
|
"adaptationRef": "docs/agents/strategic-design-skills-integration.md",
|
|
565
565
|
"targets": [
|
|
@@ -824,7 +824,7 @@
|
|
|
824
824
|
"sourceType": "git-submodule",
|
|
825
825
|
"skillPath": ".agents/skills/yss-antd-design/SKILL.md",
|
|
826
826
|
"effectiveHash": "917099caa9b76ca3cfd1d0f81f61b28111c1c54ed93e92891f81012dcd0662e2",
|
|
827
|
-
"sourceRevision": "
|
|
827
|
+
"sourceRevision": "5c1c98225bb181c7c1add8972c1e44b866205f76",
|
|
828
828
|
"upstreamHash": "08ce99d45586d5ea19e25fdd9e9a6ede16e66f18992b70b87e0cd23f92343b16",
|
|
829
829
|
"adaptationRef": "docs/agents/strategic-design-skills-integration.md",
|
|
830
830
|
"targets": [
|
|
@@ -842,7 +842,7 @@
|
|
|
842
842
|
"sourceType": "git-submodule",
|
|
843
843
|
"skillPath": ".agents/skills/yss-antdv-next-design/SKILL.md",
|
|
844
844
|
"effectiveHash": "8c55e3a815ace34fbf591e931a58970b3d58cb657e00432ca6c72938d307563b",
|
|
845
|
-
"sourceRevision": "
|
|
845
|
+
"sourceRevision": "5c1c98225bb181c7c1add8972c1e44b866205f76",
|
|
846
846
|
"upstreamHash": "2942ed52dd58a38e51f09eb11254809334aeaed08f21cd081f09f35e6647a68c",
|
|
847
847
|
"adaptationRef": "docs/agents/strategic-design-skills-integration.md",
|
|
848
848
|
"targets": [
|
|
@@ -953,7 +953,7 @@
|
|
|
953
953
|
"sourceType": "git-submodule",
|
|
954
954
|
"skillPath": ".agents/skills/yss-design-system/SKILL.md",
|
|
955
955
|
"effectiveHash": "e69597b8b6a5ce4de5bd40f67630443b8176202f6b028b2ce146a2a1d062816f",
|
|
956
|
-
"sourceRevision": "
|
|
956
|
+
"sourceRevision": "5c1c98225bb181c7c1add8972c1e44b866205f76",
|
|
957
957
|
"upstreamHash": "90aaf06b2514353152464a4d032076fa9d5ef56272d8ad526f5544f3c22a2b00",
|
|
958
958
|
"adaptationRef": "docs/agents/strategic-design-skills-integration.md",
|
|
959
959
|
"targets": [
|
|
@@ -1078,7 +1078,7 @@
|
|
|
1078
1078
|
"source": "project",
|
|
1079
1079
|
"sourceType": "local",
|
|
1080
1080
|
"skillPath": ".agents/skills/yss-frontend-scaffold-generator/SKILL.md",
|
|
1081
|
-
"effectiveHash": "
|
|
1081
|
+
"effectiveHash": "87bc0d2f0c279d3fff14ded7a07ba7b9f72a61080e7b9ca826bb452894877e4e",
|
|
1082
1082
|
"targets": [
|
|
1083
1083
|
".agents/skills",
|
|
1084
1084
|
".claude/skills",
|
|
@@ -1108,7 +1108,7 @@
|
|
|
1108
1108
|
"source": "project",
|
|
1109
1109
|
"sourceType": "local",
|
|
1110
1110
|
"skillPath": ".agents/skills/yss-implementation-contract-compiler/SKILL.md",
|
|
1111
|
-
"effectiveHash": "
|
|
1111
|
+
"effectiveHash": "abc754700c6ac1c20245923400c0e5c65e5dcf419c7d6674ec9a3fe6a997868b",
|
|
1112
1112
|
"targets": [
|
|
1113
1113
|
".agents/skills",
|
|
1114
1114
|
".claude/skills",
|
|
@@ -1213,7 +1213,7 @@
|
|
|
1213
1213
|
"source": "project",
|
|
1214
1214
|
"sourceType": "local",
|
|
1215
1215
|
"skillPath": ".agents/skills/yss-product-lifecycle/SKILL.md",
|
|
1216
|
-
"effectiveHash": "
|
|
1216
|
+
"effectiveHash": "3134144d171d3ac7a52ba37683d6a2972cf7df1f83492a5d58083567f12764af",
|
|
1217
1217
|
"targets": [
|
|
1218
1218
|
".agents/skills",
|
|
1219
1219
|
".claude/skills",
|
|
@@ -1229,7 +1229,7 @@
|
|
|
1229
1229
|
"sourceType": "git-submodule",
|
|
1230
1230
|
"skillPath": ".agents/skills/yss-prototype-stage/SKILL.md",
|
|
1231
1231
|
"effectiveHash": "0ecf3125a73d15e4c9b6b665f92535180df82ccb2255fa863ec59f3ff5d460aa",
|
|
1232
|
-
"sourceRevision": "
|
|
1232
|
+
"sourceRevision": "5c1c98225bb181c7c1add8972c1e44b866205f76",
|
|
1233
1233
|
"upstreamHash": "e8f2f3a507da6bf7be0582e4a4dd9659381da8e291755070698a1f51a4e33808",
|
|
1234
1234
|
"adaptationRef": "docs/agents/strategic-design-skills-integration.md",
|
|
1235
1235
|
"targets": [
|
|
@@ -1325,7 +1325,7 @@
|
|
|
1325
1325
|
"sourceType": "git-submodule",
|
|
1326
1326
|
"skillPath": ".agents/skills/yss-stage-decision/SKILL.md",
|
|
1327
1327
|
"effectiveHash": "63e222ec2bc572c8d66fc52143ba3862650d243f57e1e9c71214b533eba37c23",
|
|
1328
|
-
"sourceRevision": "
|
|
1328
|
+
"sourceRevision": "5c1c98225bb181c7c1add8972c1e44b866205f76",
|
|
1329
1329
|
"upstreamHash": "1eadf230f0e17bf6d209c1df1249acef3520ab29c26a80c476a2a557a224c10a",
|
|
1330
1330
|
"adaptationRef": "docs/agents/strategic-design-skills-integration.md",
|
|
1331
1331
|
"targets": [
|
|
@@ -1357,7 +1357,7 @@
|
|
|
1357
1357
|
"source": "project",
|
|
1358
1358
|
"sourceType": "local",
|
|
1359
1359
|
"skillPath": ".agents/skills/yss-ui/SKILL.md",
|
|
1360
|
-
"effectiveHash": "
|
|
1360
|
+
"effectiveHash": "db94317f36d4fccf981dfe303b8243a403f3c9a763e8a03b51673a6cc22d8370",
|
|
1361
1361
|
"targets": [
|
|
1362
1362
|
".agents/skills",
|
|
1363
1363
|
".claude/skills",
|
package/template.manifest.json
CHANGED
|
@@ -71,5 +71,47 @@
|
|
|
71
71
|
"initExcludeRootFiles": [
|
|
72
72
|
"yss-public-skills.json"
|
|
73
73
|
],
|
|
74
|
-
"initExcludePaths": []
|
|
74
|
+
"initExcludePaths": [],
|
|
75
|
+
"ownershipPolicy": {
|
|
76
|
+
"version": 1,
|
|
77
|
+
"default": "managed",
|
|
78
|
+
"rules": [
|
|
79
|
+
{ "pattern": ".git/**", "ownership": "protected" },
|
|
80
|
+
{ "pattern": ".template-source/**", "ownership": "protected" },
|
|
81
|
+
{ "pattern": ".cursor/environment.json", "ownership": "protected" },
|
|
82
|
+
{ "pattern": ".gitmodules", "ownership": "user-owned" },
|
|
83
|
+
{ "pattern": "apps/**", "ownership": "user-owned" },
|
|
84
|
+
{ "pattern": "packages/**", "ownership": "user-owned" },
|
|
85
|
+
{ "pattern": "wiki/**", "ownership": "user-owned" },
|
|
86
|
+
{ "pattern": "docs/reviews/**", "ownership": "user-owned" },
|
|
87
|
+
{ "pattern": "docs/.scratch/**", "ownership": "user-owned" },
|
|
88
|
+
{ "pattern": "yss-project.yaml", "ownership": "generated" },
|
|
89
|
+
{ "pattern": "skills-lock.json", "ownership": "generated" },
|
|
90
|
+
{ "pattern": "yss-public-skills.json", "ownership": "generated" },
|
|
91
|
+
{ "pattern": ".agents/skills/.yss-skills-manifest.json", "ownership": "generated" },
|
|
92
|
+
{ "pattern": "AGENTS.md", "ownership": "managed-customizable" },
|
|
93
|
+
{ "pattern": "CLAUDE.md", "ownership": "managed-customizable" },
|
|
94
|
+
{ "pattern": "CONTEXT.md", "ownership": "managed-customizable" },
|
|
95
|
+
{ "pattern": "DESIGN.md", "ownership": "managed-customizable" },
|
|
96
|
+
{ "pattern": "README.md", "ownership": "managed-customizable" },
|
|
97
|
+
{ "pattern": ".mcp.json", "ownership": "managed-customizable" }
|
|
98
|
+
]
|
|
99
|
+
},
|
|
100
|
+
"customizationPolicy": {
|
|
101
|
+
"version": 1,
|
|
102
|
+
"defaultStrategy": "replace-with-force",
|
|
103
|
+
"rules": [
|
|
104
|
+
{ "pattern": "CONTEXT.md", "strategy": "manual" },
|
|
105
|
+
{ "pattern": "DESIGN.md", "strategy": "manual" }
|
|
106
|
+
]
|
|
107
|
+
},
|
|
108
|
+
"generatorPolicy": {
|
|
109
|
+
"version": 1,
|
|
110
|
+
"rules": [
|
|
111
|
+
{ "pattern": "yss-project.yaml", "generatorId": "repository-identity", "generatorVersion": 1 },
|
|
112
|
+
{ "pattern": "skills-lock.json", "generatorId": "skills-lock", "generatorVersion": 1 },
|
|
113
|
+
{ "pattern": "yss-public-skills.json", "generatorId": "public-skills", "generatorVersion": 1 },
|
|
114
|
+
{ "pattern": ".agents/skills/.yss-skills-manifest.json", "generatorId": "skills-manifest", "generatorVersion": 1 }
|
|
115
|
+
]
|
|
116
|
+
}
|
|
75
117
|
}
|
package/template.snapshot.json
CHANGED
|
@@ -3,15 +3,15 @@
|
|
|
3
3
|
"templateName": "yss-spec-project-template",
|
|
4
4
|
"templateSource": "github:iloveZzz/yss-spec-project-template",
|
|
5
5
|
"templateRepository": "https://github.com/iloveZzz/yss-spec-project-template.git",
|
|
6
|
-
"requestedRef": "
|
|
7
|
-
"templateCommit": "
|
|
8
|
-
"manifestHash": "
|
|
6
|
+
"requestedRef": "4f01bef25dfac465e9ff54c71681cf4d0f74669b",
|
|
7
|
+
"templateCommit": "4f01bef25dfac465e9ff54c71681cf4d0f74669b",
|
|
8
|
+
"manifestHash": "f7520dfdfa388b19ea47cb893c2daab8936019783aa84589f13921e459702280",
|
|
9
9
|
"encodedPaths": {
|
|
10
10
|
".codex/skills/data-analytics/.gitignore": ".codex/skills/data-analytics/__yss_dotfile__.gitignore",
|
|
11
11
|
".codex/skills/product-design/.npmignore": ".codex/skills/product-design/__yss_dotfile__.npmignore",
|
|
12
12
|
".codex/skills/product-design/templates/prototype/.npmrc": ".codex/skills/product-design/templates/prototype/__yss_dotfile__.npmrc",
|
|
13
13
|
".gitignore": "__yss_dotfile__.gitignore"
|
|
14
14
|
},
|
|
15
|
-
"snapshotHash": "
|
|
16
|
-
"generatedAt": "2026-09-
|
|
15
|
+
"snapshotHash": "3c5bb9dea7cc8f5bf8559f49ee7b31fea830bff51839dfc49e233e7afe9f948d",
|
|
16
|
+
"generatedAt": "2026-09-07T03:27:16.902Z"
|
|
17
17
|
}
|