@givolt/i3-composer-agent 2.3.1 → 2.3.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/.agents/plugins/marketplace.json +1 -1
- package/.claude/commands/i3-prototype.md +5 -7
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.gemini/commands/i3-prototype.toml +5 -7
- package/README.md +16 -7
- package/bin/agent-targets.json +4 -2
- package/bin/i3-composer-agent.cjs +74 -7
- package/bin/install.cjs +16 -1
- package/bin/native-layout.cjs +1 -1
- package/bin/native-migration.cjs +27 -3
- package/bin/tool-selector.cjs +24 -7
- package/bin/wizard.cjs +89 -15
- package/commands/i3-prototype.toml +5 -7
- package/docs/codebuddy-setup.md +91 -3
- package/docs/codex-setup.md +6 -0
- package/docs/other-hosts.md +1 -1
- package/docs/ui-prototype-native/MAIN-SESSION-REPORT.md +66 -0
- package/docs/ui-prototype-native/USAGE.md +93 -0
- package/docs/workbuddy-setup.md +6 -0
- package/evals/cases/i3-frontend-ui-engineering.json +16 -0
- package/evals/cases/i3-ui-prototype.json +109 -0
- package/evals/cases/i3-using-agent-skills.json +28 -0
- package/evals/fixtures/i3-ui-prototype/admin-list/assets/reference.css +3 -0
- package/evals/fixtures/i3-ui-prototype/admin-list/assets/reference.js +8 -0
- package/evals/fixtures/i3-ui-prototype/admin-list/capability-definition.json +67 -0
- package/evals/fixtures/i3-ui-prototype/admin-list/index.html +89 -0
- package/evals/fixtures/i3-ui-prototype/admin-list/readme.md +52 -0
- package/evals/fixtures/i3-ui-prototype/admin-list/template.json +8 -0
- package/evals/zh-CN/cases/i3-frontend-ui-engineering.json +17 -0
- package/evals/zh-CN/cases/i3-ui-prototype.json +115 -0
- package/evals/zh-CN/cases/i3-using-agent-skills.json +30 -0
- package/hooks/codebuddy-upstream.cjs +93 -0
- package/hooks/i3-prototype.cjs +12 -0
- package/hooks/manage.cjs +162 -0
- package/package.json +61 -1
- package/plugin.json +1 -1
- package/scripts/design-provider-test.js +7 -0
- package/scripts/enterprise-template-test.js +27 -1
- package/scripts/lib/prototype-fixture.cjs +17 -0
- package/scripts/prototype-cache-test.js +55 -0
- package/scripts/prototype-generation-entry-test.js +6 -0
- package/scripts/prototype-generation-evidence-test.js +10 -0
- package/scripts/prototype-generation-native-test.js +78 -0
- package/scripts/prototype-generation-test.js +99 -0
- package/scripts/prototype-inventory-test.js +43 -0
- package/scripts/prototype-locations-test.js +79 -0
- package/scripts/prototype-project-test.js +183 -0
- package/scripts/ui-prototype-codebuddy-hook-test.js +106 -0
- package/scripts/ui-prototype-content-test.js +123 -0
- package/scripts/ui-prototype-dependencies-test.js +213 -0
- package/scripts/ui-prototype-devtools-test.js +53 -0
- package/scripts/ui-prototype-execution-evidence-test.js +62 -0
- package/scripts/ui-prototype-execution-gates-test.js +66 -0
- package/scripts/ui-prototype-execution-policy-test.js +42 -0
- package/scripts/ui-prototype-execution-runtime-test.js +27 -0
- package/scripts/ui-prototype-ide-source-test.js +73 -0
- package/scripts/ui-prototype-native-test.js +504 -0
- package/scripts/ui-prototype-registration-test.js +53 -0
- package/scripts/ui-prototype-template-test.js +261 -0
- package/scripts/ui-prototype-test.js +10 -0
- package/scripts/ui-prototype-transcript-auto-test.js +67 -0
- package/skills/i3-frontend-ui-engineering/SKILL.md +1 -1
- package/skills/i3-incremental-implementation/SKILL.md +1 -1
- package/skills/i3-ui-prototype/SKILL.md +158 -19
- package/skills/i3-ui-prototype/assets/capabilities/generation.json +833 -0
- package/skills/i3-ui-prototype/assets/capabilities/management.json +294 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/acceptance.json +51 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/component-map.json +13 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/generation-component-map.json +300 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/legacy-starter/index.html +11 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/legacy-starter/package-lock.json +2298 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/legacy-starter/package.json +22 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/legacy-starter/public/prototype-config.json +6 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/legacy-starter/src/App.vue +136 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/legacy-starter/src/components/DeviceEditor.vue +111 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/legacy-starter/src/config.ts +19 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/legacy-starter/src/content.ts +28 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/legacy-starter/src/domain/devices.ts +50 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/legacy-starter/src/domain/theme.ts +25 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/legacy-starter/src/main.ts +18 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/legacy-starter/src/style.css +50 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/legacy-starter/src/useDevices.ts +66 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/legacy-starter/tests/devices.test.ts +44 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/legacy-starter/tests/theme.test.ts +26 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/legacy-starter/tsconfig.json +15 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/legacy-starter/vite.config.ts +4 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/profile.json +174 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/readme.md +61 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/starter/index.html +11 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/starter/package-lock.json +2298 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/starter/package.json +22 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/starter/public/prototype-config.json +21 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/starter/src/App.vue +62 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/starter/src/PrototypeShell.vue +59 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/starter/src/config.ts +23 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/starter/src/domain/presentation.ts +29 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/starter/src/domain/theme.ts +25 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/starter/src/main.ts +18 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/starter/src/style.css +26 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/starter/tests/presentation.test.ts +23 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/starter/tests/theme.test.ts +26 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/starter/tsconfig.json +15 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/starter/vite.config.ts +4 -0
- package/skills/i3-ui-prototype/assets/hosts/codebuddy/profile.json +13 -0
- package/skills/i3-ui-prototype/assets/optional-skills/host-targets.json +739 -0
- package/skills/i3-ui-prototype/assets/optional-skills/sources.json +27 -0
- package/skills/i3-ui-prototype/assets/templates/catalog.js +149 -0
- package/skills/i3-ui-prototype/assets/templates/catalog.json +2 -1
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/capability-definition.json +294 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/component-inventory.json +707 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/generation.json +49 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/index.html +507 -2
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/offline-build.json +265 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/offline.js +8888 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/template-inventory.json +28791 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/template.json +9 -0
- package/skills/i3-ui-prototype/assets/templates/gallery/index.html +5 -5
- package/skills/i3-ui-prototype/assets/templates/generation-options.js +42 -0
- package/skills/i3-ui-prototype/assets/templates/index.html +3 -0
- package/skills/i3-ui-prototype/assets/templates/presentation.html +17 -0
- package/skills/i3-ui-prototype/references/artifact-contract.md +26 -1
- package/skills/i3-ui-prototype/references/content-template.md +81 -0
- package/skills/i3-ui-prototype/references/enterprise-admin.md +6 -0
- package/skills/i3-ui-prototype/references/entry-contract.md +71 -0
- package/skills/i3-ui-prototype/references/execution-evidence.md +66 -0
- package/skills/i3-ui-prototype/references/expert-adapter.md +33 -5
- package/skills/i3-ui-prototype/references/framework-contract.md +76 -0
- package/skills/i3-ui-prototype/references/generation-contract.md +122 -0
- package/skills/i3-ui-prototype/references/interaction-acceptance.md +169 -0
- package/skills/i3-ui-prototype/references/legacy-native.md +58 -0
- package/skills/i3-ui-prototype/references/optional-skills.md +63 -0
- package/skills/i3-ui-prototype/references/project-layout.md +92 -0
- package/skills/i3-ui-prototype/references/template-adapters.md +7 -1
- package/skills/i3-ui-prototype/references/workflow-contract.md +62 -4
- package/skills/i3-ui-prototype/scripts/adapters/easel-html.cjs +1 -1
- package/skills/i3-ui-prototype/scripts/browser-workspace.cjs +72 -0
- package/skills/i3-ui-prototype/scripts/codebuddy-context.cjs +75 -0
- package/skills/i3-ui-prototype/scripts/codebuddy-hook.cjs +157 -0
- package/skills/i3-ui-prototype/scripts/codebuddy-ide-source.cjs +69 -0
- package/skills/i3-ui-prototype/scripts/codebuddy-registration.cjs +123 -0
- package/skills/i3-ui-prototype/scripts/content-template.cjs +98 -0
- package/skills/i3-ui-prototype/scripts/enterprise-template.cjs +13 -6
- package/skills/i3-ui-prototype/scripts/execution-assertions.cjs +43 -0
- package/skills/i3-ui-prototype/scripts/execution-evidence.cjs +146 -0
- package/skills/i3-ui-prototype/scripts/execution-gates.cjs +147 -0
- package/skills/i3-ui-prototype/scripts/execution-policy.cjs +41 -0
- package/skills/i3-ui-prototype/scripts/execution-runtime.cjs +60 -0
- package/skills/i3-ui-prototype/scripts/generation-assets.cjs +18 -0
- package/skills/i3-ui-prototype/scripts/generation-contract.cjs +214 -0
- package/skills/i3-ui-prototype/scripts/generation-evidence.cjs +50 -0
- package/skills/i3-ui-prototype/scripts/generation-replan.cjs +34 -0
- package/skills/i3-ui-prototype/scripts/hosts/codebuddy.cjs +16 -0
- package/skills/i3-ui-prototype/scripts/manage-baseline.cjs +1 -1
- package/skills/i3-ui-prototype/scripts/native-browser.cjs +785 -0
- package/skills/i3-ui-prototype/scripts/native-build.cjs +91 -0
- package/skills/i3-ui-prototype/scripts/native-delivery.cjs +103 -0
- package/skills/i3-ui-prototype/scripts/native-devtools.cjs +92 -0
- package/skills/i3-ui-prototype/scripts/native-files.cjs +35 -0
- package/skills/i3-ui-prototype/scripts/native-pilot-lib.cjs +292 -0
- package/skills/i3-ui-prototype/scripts/native-pilot.cjs +50 -0
- package/skills/i3-ui-prototype/scripts/optional-skill-install.cjs +203 -0
- package/skills/i3-ui-prototype/scripts/project-registry.cjs +126 -0
- package/skills/i3-ui-prototype/scripts/prototype-dependencies.cjs +81 -0
- package/skills/i3-ui-prototype/scripts/prototype-hooks.cjs +87 -0
- package/skills/i3-ui-prototype/scripts/prototype-lib.cjs +31 -13
- package/skills/i3-ui-prototype/scripts/prototype-locations.cjs +69 -0
- package/skills/i3-ui-prototype/scripts/sync-template.cjs +19 -0
- package/skills/i3-ui-prototype/scripts/template-contract.cjs +407 -0
- package/skills/i3-ui-prototype/scripts/template-inventory.cjs +78 -0
- package/skills/i3-ui-prototype/scripts/validate-result.cjs +117 -0
- package/skills/i3-using-agent-skills/SKILL.md +2 -5
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const fs = require('node:fs'), path = require('node:path'), crypto = require('node:crypto'), assert = require('node:assert/strict');
|
|
3
|
+
const hash = value => crypto.createHash('sha256').update(JSON.stringify(value)).digest('hex');
|
|
4
|
+
const copy = value => JSON.parse(JSON.stringify(value));
|
|
5
|
+
function resource(snapshot, name) {
|
|
6
|
+
const file = snapshot.files.find(item => item.path === name);
|
|
7
|
+
assert.ok(file, `Template generation resource is missing: ${name}`);
|
|
8
|
+
return JSON.parse(Buffer.from(file.content, 'base64').toString('utf8'));
|
|
9
|
+
}
|
|
10
|
+
function definition() { return JSON.parse(fs.readFileSync(path.join(__dirname, '../assets/capabilities/generation.json'), 'utf8')); }
|
|
11
|
+
function source(requirement) {
|
|
12
|
+
return requirement?.source && ['type', 'reference', 'statement'].every(key => typeof requirement.source[key] === 'string' && requirement.source[key].trim());
|
|
13
|
+
}
|
|
14
|
+
function validateParameters(capability, value) {
|
|
15
|
+
assert.ok(value && typeof value === 'object' && !Array.isArray(value), 'Capability parameters must be an object');
|
|
16
|
+
for (const [key, item] of Object.entries(value)) {
|
|
17
|
+
const rule = capability.parameters[key]; assert.ok(rule, `Unsupported ${capability.id} parameter: ${key}`);
|
|
18
|
+
if (rule.enum) assert.ok(rule.enum.includes(item), `Invalid ${capability.id} parameter: ${key}`);
|
|
19
|
+
if (rule.boolean) assert.equal(typeof item, 'boolean');
|
|
20
|
+
if (rule.string) assert.ok(typeof item === 'string' && item.trim(), `Invalid string parameter: ${key}`);
|
|
21
|
+
if (rule.integer) assert.ok(Number.isInteger(item) && item >= rule.integer, `Invalid integer parameter: ${key}`);
|
|
22
|
+
if (rule.strings) assert.ok(Array.isArray(item) && item.every(value => typeof value === 'string' && value.trim()), `Invalid list parameter: ${key}`);
|
|
23
|
+
if (rule.positiveIntegers) assert.ok(Array.isArray(item) && item.length >= 2 && new Set(item).size === item.length && item.every(value => Number.isInteger(value) && value > 0), `Invalid page-size parameter: ${key}`);
|
|
24
|
+
}
|
|
25
|
+
if (capability.id === 'pagination' && Object.keys(value).length) require('./template-contract.cjs').resolvePaginationParameters(value);
|
|
26
|
+
return copy(value);
|
|
27
|
+
}
|
|
28
|
+
function reference(inventory, start) {
|
|
29
|
+
const fragments = new Map(inventory.fragments.map(fragment => [fragment.file, fragment])), visited = new Set(), nodes = [], events = [], elements = [], capabilities = new Set();
|
|
30
|
+
function visit(file) {
|
|
31
|
+
if (visited.has(file)) return; visited.add(file);
|
|
32
|
+
const fragment = fragments.get(file); assert.ok(fragment, `Reference fragment is missing: ${file}`);
|
|
33
|
+
for (const node of fragment.nodes) {
|
|
34
|
+
nodes.push(node.id);
|
|
35
|
+
for (const [event, expression] of Object.entries(node.attributes)) if (event.startsWith('data-on-')) events.push({ nodeId: node.id, event, expression });
|
|
36
|
+
const componentName = node.attributes['data-component'], component = inventory.components[componentName];
|
|
37
|
+
const nodeEvents = Object.entries(node.attributes).filter(([key]) => key.startsWith('data-on-')).map(([event, expression]) => ({ event, expression }));
|
|
38
|
+
if (nodeEvents.length || (component && component.classification.kind !== 'icon' && component.classification.capabilities.some(id => id !== 'review-tools'))) elements.push({ nodeId: node.id, fragment: file, component: componentName || null, capabilities: (component?.classification.capabilities || []).filter(id => id !== 'review-tools'), events: nodeEvents });
|
|
39
|
+
if (component) {
|
|
40
|
+
for (const capability of component.classification.capabilities) capabilities.add(capability);
|
|
41
|
+
if (component.classification.source) visit(component.classification.source);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
visit(start);
|
|
46
|
+
return { fragments: [...visited], nodes, events, elements, capabilities: [...capabilities].sort() };
|
|
47
|
+
}
|
|
48
|
+
function navigation(items, pages) {
|
|
49
|
+
if (items === undefined) return pages.map(page => ({ key: page.id, label: page.title }));
|
|
50
|
+
const seen = new Set(), groups = new Set();
|
|
51
|
+
function walk(values) {
|
|
52
|
+
assert.ok(Array.isArray(values) && values.length, 'Navigation entries must be a nonempty array');
|
|
53
|
+
return values.map(item => {
|
|
54
|
+
if (item.children) {
|
|
55
|
+
assert.ok(!item.pageId && /^[a-z][a-z0-9-]{0,63}$/.test(item.id || '') && item.title?.trim() && !groups.has(item.id), 'Invalid or duplicate navigation group'); groups.add(item.id);
|
|
56
|
+
return { key: `@${item.id}`, label: item.title, children: walk(item.children) };
|
|
57
|
+
}
|
|
58
|
+
const page = pages.find(page => page.id === item.pageId);
|
|
59
|
+
assert.ok(page && !seen.has(page.id), 'Duplicate or unknown navigation page'); seen.add(page.id);
|
|
60
|
+
return { key: page.id, label: item.title || page.title };
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
const result = walk(items); assert.equal(seen.size, pages.length, 'Navigation omits a declared page'); return result;
|
|
64
|
+
}
|
|
65
|
+
function compile(snapshot, scope, options = {}) {
|
|
66
|
+
assert.equal(scope.schemaVersion, 3, 'Generation contracts require sourced scope schemaVersion=3');
|
|
67
|
+
const meta = resource(snapshot, 'generation.json'), rules = copy(options.definition || definition());
|
|
68
|
+
assert.equal(meta.schemaVersion, 1, 'Unsupported template generation version'); assert.equal(rules.schemaVersion, 1);
|
|
69
|
+
assert.equal(meta.templateId, snapshot.id, 'Generation metadata belongs to a different template');
|
|
70
|
+
assert.equal(hash(rules), meta.definitionDigest, 'Generation catalog differs from the frozen template rule identity');
|
|
71
|
+
assert.ok(meta.frameworks.includes(options.framework || 'vue-antdv-next'), 'Unsupported template/framework choice');
|
|
72
|
+
const presentation = { ...meta.defaults, ...scope.generation?.presentation };
|
|
73
|
+
for (const key of Object.keys(presentation)) assert.ok(Object.hasOwn(meta.choices[key] || {}, presentation[key]), `Unsupported presentation choice: ${key}=${presentation[key]}`);
|
|
74
|
+
const requirements = new Map((scope.requirements || []).map(item => [item.id, item]));
|
|
75
|
+
assert.ok(requirements.size === scope.requirements.length && requirements.size && [...requirements.values()].every(item => typeof item.id === 'string' && item.id.trim() && source(item)), 'Every generation requirement needs a unique identity and source');
|
|
76
|
+
const inventory = require('./template-inventory.cjs').verifySnapshot(snapshot), bindings = [], pages = [], coverage = [], decisions = scope.generation?.decisions || [];
|
|
77
|
+
assert.ok(Array.isArray(decisions), 'Generation applicability decisions must be an array');
|
|
78
|
+
const keys = new Set();
|
|
79
|
+
for (const decision of decisions) {
|
|
80
|
+
const key = `${decision.pageId}/${decision.regionId || "*"}/${decision.capabilityId}`; assert.ok(!keys.has(key), 'Duplicate generation applicability decision'); keys.add(key);
|
|
81
|
+
assert.ok(rules.capabilities[decision.capabilityId], 'Unknown generation capability decision');
|
|
82
|
+
assert.ok(['adopted', 'not-applicable'].includes(decision.decision), 'Unsupported generation decision');
|
|
83
|
+
if (decision.decision === 'not-applicable') { assert.ok(!decision.regionId && !Object.keys(decision.parameters || {}).length, 'Capability exclusion applies to the page and has no ignored parameters'); assert.ok(!decisions.some(item => item !== decision && item.pageId === decision.pageId && item.capabilityId === decision.capabilityId), 'Conflicting capability applicability decisions'); }
|
|
84
|
+
const requirement = requirements.get(decision.requirementId);
|
|
85
|
+
assert.ok(decision.reason?.trim() && source(requirement) && (decision.pageId === 'app-shell' || requirement.pages?.includes(decision.pageId)), 'Generation decision requires a reason and matching requirement source');
|
|
86
|
+
}
|
|
87
|
+
function bind(pageId, regionId, capabilityId, parameters = {}) {
|
|
88
|
+
assert.ok(rules.capabilities[capabilityId], `Unknown pattern capability: ${capabilityId}`);
|
|
89
|
+
const id = `${pageId}/${regionId}/${capabilityId}`;
|
|
90
|
+
const defaults = parameters.mode === 'fixed' ? { minimumTestPages: 3 } : rules.capabilities[capabilityId].defaults;
|
|
91
|
+
if (!bindings.some(item => item.id === id)) bindings.push({ id, pageId, regionId, capabilityId, parameters: validateParameters(rules.capabilities[capabilityId], { ...defaults, ...parameters }) });
|
|
92
|
+
}
|
|
93
|
+
for (const capability of rules.core) bind('app-shell', 'shell', capability);
|
|
94
|
+
if (meta.shell) coverage.push({ pageId: 'app-shell', ...reference(inventory, meta.shell) });
|
|
95
|
+
assert.ok(scope.pages?.length, 'Generation pages are required');
|
|
96
|
+
for (const page of scope.pages) {
|
|
97
|
+
assert.match(page.id || '', /^[a-z][a-z0-9-]{0,63}$/); assert.ok(page.id !== 'app-shell' && page.title?.trim(), 'Business page identity/title required');
|
|
98
|
+
assert.ok(!pages.some(item => item.id === page.id), 'Duplicate generation page');
|
|
99
|
+
const selected = snapshot.index.pages.find(item => item.id === page.referencePage); assert.ok(selected, `Unknown reference page: ${page.referencePage}`);
|
|
100
|
+
assert.ok(page.regions?.length, 'Generation regions are required');
|
|
101
|
+
const regionIds = new Set();
|
|
102
|
+
for (const region of page.regions) {
|
|
103
|
+
assert.match(region.id || '', /^[a-z][a-z0-9-]{0,63}$/);
|
|
104
|
+
assert.ok(!regionIds.has(region.id), 'Duplicate generation region'); regionIds.add(region.id);
|
|
105
|
+
const pattern = region.pattern || region.mode;
|
|
106
|
+
assert.ok(meta.patterns.includes(pattern) && rules.patterns[pattern], `Unsupported page pattern: ${pattern}`);
|
|
107
|
+
const selectedCapabilities = new Set(rules.patterns[pattern].required);
|
|
108
|
+
const editPresentation = region.parameters?.edit?.presentation || (['form-page','wizard','permissions'].includes(pattern) ? 'page' : region.parameters?.dialog?.presentation || 'drawer');
|
|
109
|
+
if (selectedCapabilities.has('edit')) {
|
|
110
|
+
if (pattern === 'form-page') assert.equal(editPresentation, 'page', 'A form-page requires page presentation');
|
|
111
|
+
if (pattern === 'edit-form') assert.ok(['drawer','modal'].includes(editPresentation), 'An edit-form requires dialog presentation');
|
|
112
|
+
if (editPresentation !== 'page') selectedCapabilities.add('dialog');
|
|
113
|
+
if (region.parameters?.dialog?.presentation) assert.equal(region.parameters.dialog.presentation, editPresentation, 'Edit and dialog presentation must agree');
|
|
114
|
+
}
|
|
115
|
+
for (const key of Object.keys(region.parameters || {})) {
|
|
116
|
+
assert.ok(rules.capabilities[key], `Unsupported capability parameter group: ${key}`);
|
|
117
|
+
assert.ok(selectedCapabilities.has(key) || decisions.some(item => item.pageId === page.id && item.regionId === region.id && item.capabilityId === key && item.decision === 'adopted'), `Unused capability parameter group: ${key}`);
|
|
118
|
+
}
|
|
119
|
+
for (const capability of selectedCapabilities) {
|
|
120
|
+
const selectedDefaults = capability === 'hierarchy' ? { kind: pattern === 'tree-table' ? 'tree-table' : pattern === 'permissions' ? 'permission-tree' : 'filter-tree', selectionPolicy: pattern === 'permissions' ? 'cascade' : pattern === 'tree-table' ? 'independent' : 'single' } : capability === 'edit' ? { presentation: editPresentation } : capability === 'dialog' && selectedCapabilities.has('edit') ? { presentation: editPresentation } : {};
|
|
121
|
+
bind(page.id, region.id, capability, { ...selectedDefaults, ...region.parameters?.[capability] });
|
|
122
|
+
}
|
|
123
|
+
for (const [kind, items] of Object.entries({ field: region.fields || [], action: region.actions || [] })) {
|
|
124
|
+
assert.ok(Array.isArray(items), 'Declare sourced field/action arrays'); const elementIds = new Set();
|
|
125
|
+
for (const item of items) {
|
|
126
|
+
assert.ok(/^[a-z][a-z0-9-]{0,63}$/.test(item.id || '') && !elementIds.has(item.id), 'Invalid or duplicate field/action identity'); elementIds.add(item.id);
|
|
127
|
+
assert.ok(item.label?.trim() && source(requirements.get(item.requirementId)) && requirements.get(item.requirementId).pages?.includes(page.id), 'Business field/action requires a label and matching requirement source');
|
|
128
|
+
if (kind === 'field') assert.ok(['text','textarea','select','number','checkbox','radio','date','time','tree-select','read-only'].includes(item.kind), 'Unsupported business field kind');
|
|
129
|
+
else assert.ok(['create','read','update','delete','submit','state-change','other'].includes(item.kind) && ['page','row','selected','node'].includes(item.target) && item.outcome?.trim(), 'Business action needs kind, target and expected outcome');
|
|
130
|
+
bindings.push({ id: `${page.id}/${region.id}/${kind}/${item.id}`, pageId: page.id, regionId: region.id, capabilityId: 'interaction', kind, parameters: copy(item) });
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
pages.push({ id: page.id, title: page.title, referencePage: page.referencePage, regions: page.regions.map(region => ({ id: region.id, pattern: region.pattern || region.mode, fields: copy(region.fields || []), actions: copy(region.actions || []) })) });
|
|
135
|
+
coverage.push({ pageId: page.id, ...reference(inventory, selected.fragment) });
|
|
136
|
+
}
|
|
137
|
+
for (const decision of decisions) {
|
|
138
|
+
assert.ok(decision.pageId === 'app-shell' || pages.some(page => page.id === decision.pageId), 'Decision page is not declared');
|
|
139
|
+
const alreadyRequired = bindings.some(item => (item.pageId === decision.pageId || item.pageId === 'app-shell') && item.capabilityId === decision.capabilityId);
|
|
140
|
+
assert.ok(!(alreadyRequired && decision.decision === 'not-applicable'), 'Cannot exempt a required selected-pattern capability');
|
|
141
|
+
if (decision.decision !== 'adopted') continue;
|
|
142
|
+
if (!decision.regionId) {
|
|
143
|
+
assert.ok(alreadyRequired, 'An adopted capability needs a declared region');
|
|
144
|
+
assert.ok(!Object.keys(decision.parameters || {}).length, 'Required capability parameters belong on the selected region');
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
const page = scope.pages.find(item => item.id === decision.pageId);
|
|
148
|
+
const region = decision.pageId === 'app-shell' && decision.regionId === 'shell' ? { id: 'shell' } : page?.regions.find(item => item.id === decision.regionId);
|
|
149
|
+
assert.ok(region, 'An adopted capability needs a declared region');
|
|
150
|
+
if (bindings.some(item => item.pageId === decision.pageId && item.regionId === region.id && item.capabilityId === decision.capabilityId)) assert.ok(!Object.keys(decision.parameters || {}).length, 'Required capability parameters belong on the selected region');
|
|
151
|
+
else bind(decision.pageId, region.id, decision.capabilityId, { ...region.parameters?.[decision.capabilityId], ...decision.parameters });
|
|
152
|
+
}
|
|
153
|
+
const pendingDecisions = [];
|
|
154
|
+
for (const item of coverage) for (const capabilityId of item.capabilities) {
|
|
155
|
+
if (capabilityId === 'review-tools') continue;
|
|
156
|
+
assert.ok(rules.capabilities[capabilityId], `Unmapped reference capability: ${capabilityId}`);
|
|
157
|
+
if (!bindings.some(binding => (binding.pageId === item.pageId || binding.pageId === 'app-shell') && binding.capabilityId === capabilityId) && !decisions.some(decision => decision.pageId === item.pageId && decision.capabilityId === capabilityId)) pendingDecisions.push({ pageId: item.pageId, capabilityId });
|
|
158
|
+
}
|
|
159
|
+
// Each functional reference element is accounted for, including elements whose
|
|
160
|
+
// capability is already core. Adopting a generic capability never accounts for
|
|
161
|
+
// every example button, field or event implicitly.
|
|
162
|
+
const referenceDecisions = scope.generation?.referenceDecisions || [], accounted = new Set();
|
|
163
|
+
assert.ok(Array.isArray(referenceDecisions), 'Reference element decisions must be an array');
|
|
164
|
+
for (const decision of referenceDecisions) {
|
|
165
|
+
const requirement = requirements.get(decision.requirementId), selected = coverage.find(item => item.pageId === decision.pageId);
|
|
166
|
+
assert.ok(selected && source(requirement) && (decision.pageId === 'app-shell' || requirement.pages?.includes(decision.pageId)) && decision.reason?.trim(), 'Reference element decision requires matching requirement source and reason');
|
|
167
|
+
assert.ok(['mapped','not-applicable'].includes(decision.decision) && Array.isArray(decision.nodeIds) && decision.nodeIds.length, 'Explicit reference node IDs and disposition required');
|
|
168
|
+
for (const nodeId of decision.nodeIds) {
|
|
169
|
+
const key = `${decision.pageId}/${nodeId}`, element = selected.elements.find(item => item.nodeId === nodeId);
|
|
170
|
+
assert.ok(element && !accounted.has(key), 'Unknown or duplicate reference element disposition'); accounted.add(key);
|
|
171
|
+
if (decision.decision === 'mapped') {
|
|
172
|
+
assert.ok(Array.isArray(decision.bindingIds) && decision.bindingIds.length, 'Mapped reference elements need actual binding IDs');
|
|
173
|
+
for (const bindingId of decision.bindingIds) assert.ok(bindings.some(binding => binding.id === bindingId && (binding.pageId === decision.pageId || binding.pageId === 'app-shell')), 'Reference element binding belongs to another page or is absent');
|
|
174
|
+
const relevant = element.capabilities.length ? element.capabilities : ['interaction'];
|
|
175
|
+
assert.ok(bindings.some(binding => decision.bindingIds.includes(binding.id) && relevant.includes(binding.capabilityId)), 'Reference element mapping needs a matching semantic capability');
|
|
176
|
+
} else assert.ok(!decision.bindingIds?.length, 'Excluded reference elements cannot claim implementation');
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
const pendingReferenceElements = coverage.flatMap(item => item.elements.filter(element => !accounted.has(`${item.pageId}/${element.nodeId}`)).map(element => ({ pageId: item.pageId, ...element })));
|
|
180
|
+
function scenarioExpectation(binding, scenario) {
|
|
181
|
+
if (binding.capabilityId === 'pagination' && scenario.id === 'page-size' && binding.parameters.mode === 'fixed') return `固定容量 ${binding.parameters.fixedPageSize} 不可更改,界面说明正确;翻页、总数和选择符合固定容量约定。`;
|
|
182
|
+
if (binding.capabilityId === 'columns' && scenario.id === 'persistence-scroll' && !binding.parameters.persist) return '列配置按声明仅在本轮生效,刷新恢复默认;宽表可滚动且操作列可达。';
|
|
183
|
+
if (binding.capabilityId === 'dialog' && scenario.id === 'lifetime' && binding.parameters.presentation === 'modal') return '弹窗打开、关闭和进出动效生命周期完整,未提前卸载,符合固定弹窗呈现。';
|
|
184
|
+
return scenario.expected;
|
|
185
|
+
}
|
|
186
|
+
const scenarios = bindings.flatMap(binding => binding.kind ? [{ id: `generation/${binding.id}`, bindingId: binding.id, pageId: binding.pageId, regionId: binding.regionId, capabilityId: binding.capabilityId, steps: [`在当前构建中操作 ${binding.parameters.label},核对身份、状态和需求来源。`], expected: binding.kind === 'action' ? binding.parameters.outcome : `字段 ${binding.parameters.label} 的类型、必填、只读、校验与联动符合固定字段声明。`, parameters: copy(binding.parameters) }] : rules.capabilities[binding.capabilityId].scenarios.map(scenario => ({ ...copy(scenario), expected: scenarioExpectation(binding, scenario), id: `generation/${binding.id}/${scenario.id}`, bindingId: binding.id, pageId: binding.pageId, regionId: binding.regionId, capabilityId: binding.capabilityId, parameters: copy(binding.parameters) })));
|
|
187
|
+
for (const decision of referenceDecisions.filter(item => item.decision === 'mapped')) for (const nodeId of decision.nodeIds) {
|
|
188
|
+
const element = coverage.find(item => item.pageId === decision.pageId).elements.find(item => item.nodeId === nodeId);
|
|
189
|
+
const relevant = element.capabilities.length ? element.capabilities : ['interaction'];
|
|
190
|
+
const binding = bindings.find(item => decision.bindingIds.includes(item.id) && relevant.includes(item.capabilityId));
|
|
191
|
+
scenarios.push({ id: `generation/reference/${decision.pageId}/${hash(nodeId).slice(0,16)}`, bindingId: binding.id, pageId: decision.pageId, regionId: binding.regionId, capabilityId: binding.capabilityId, referenceNodeId: nodeId, referenceEvents: copy(element.events), requirementId: decision.requirementId, steps: ['在对应实际页面操作或检查本参考节点映射的控件,覆盖其采用的交互和状态。'], expected: `核对实际控件、对象、交互结果和状态符合需求与本次取舍:${decision.reason};不能用同能力下的另一个控件代替。`, parameters: { bindingIds: copy(decision.bindingIds) } });
|
|
192
|
+
}
|
|
193
|
+
return { schemaVersion: 1, definition: rules, template: { id: snapshot.id, version: snapshot.version, digest: snapshot.digest }, presentation, navigation: navigation(scope.generation?.navigation, pages), pages, bindings, decisions: copy(decisions), referenceCoverage: coverage, pendingDecisions, referenceDecisions: copy(referenceDecisions), pendingReferenceElements, scenarios };
|
|
194
|
+
}
|
|
195
|
+
function verify(snapshot, scope, fixed) {
|
|
196
|
+
assert.deepEqual(fixed, compile(snapshot, scope, { definition: fixed.definition }), 'Fixed generation contract differs from selected patterns, sourced decisions or customer choices');
|
|
197
|
+
return { status: 'passed', bindings: fixed.bindings.length, scenarios: fixed.scenarios.length, pendingDecisions: fixed.pendingDecisions.length, pendingReferenceElements: fixed.pendingReferenceElements.length };
|
|
198
|
+
}
|
|
199
|
+
function view(contract, options = {}) {
|
|
200
|
+
if (options.details) return contract;
|
|
201
|
+
if (options.page) assert.ok(options.page === 'app-shell' || contract.pages.some(page => page.id === options.page), 'Unknown generation page');
|
|
202
|
+
const coverage = contract.referenceCoverage.filter(item => !options.page || item.pageId === options.page);
|
|
203
|
+
if (options.fragment) assert.ok(coverage.some(item => item.fragments.includes(options.fragment)), 'Unknown reference fragment for selected page');
|
|
204
|
+
const pending = contract.pendingReferenceElements.filter(item => !options.page || item.pageId === options.page), groups = new Map();
|
|
205
|
+
for (const item of pending) { const key = `${item.pageId}/${item.fragment}`; if (!groups.has(key)) groups.set(key, { pageId: item.pageId, fragment: item.fragment, pending: 0 }); groups.get(key).pending++; }
|
|
206
|
+
const scenarios = contract.scenarios.filter(item => !options.page || item.pageId === options.page);
|
|
207
|
+
return { status: 'planned', readOnly: true, template: contract.template, presentation: contract.presentation,
|
|
208
|
+
pages: contract.pages.filter(item => !options.page || item.id === options.page), bindings: contract.bindings.filter(item => !options.page || item.pageId === options.page),
|
|
209
|
+
pendingDecisions: contract.pendingDecisions.filter(item => !options.page || item.pageId === options.page),
|
|
210
|
+
referenceReview: { pendingCount: pending.length, fragments: [...groups.values()], ...(options.fragment ? { elements: pending.filter(item => item.fragment === options.fragment) } : {}) },
|
|
211
|
+
acceptance: { scenarios: scenarios.length, ...(options.page ? { scenarioIds: scenarios.map(item => item.id) } : {}) },
|
|
212
|
+
next: 'Use --page <business-page-id|app-shell> --fragment <reference-path> for nodes; --details returns the full fixed contract.' };
|
|
213
|
+
}
|
|
214
|
+
module.exports = { compile, verify, definition, resource, view };
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// Structural evidence validation cannot prove an actual host/MCP invocation or UI outcome.
|
|
3
|
+
const {fs,path,assert,json,inside,hash}=require('./native-files.cjs');
|
|
4
|
+
const nonempty=(value,label)=>assert.ok(typeof value==='string'&&value.trim(),`${label} required`);
|
|
5
|
+
const exact=(actual,expected,label)=>assert.deepEqual([...actual].sort(),[...expected].sort(),`${label} differs from fixed generation bindings/scenarios`);
|
|
6
|
+
function framework(){return json(path.join(__dirname,'../assets/frameworks/vue-antdv-next/generation-component-map.json'));}
|
|
7
|
+
function initialize(generation){return {schemaVersion:1,bindings:generation.bindings.map(binding=>({bindingId:binding.id,status:'pending',components:[],files:[],scenarioIds:generation.scenarios.filter(s=>s.bindingId===binding.id).map(s=>s.id)}))};}
|
|
8
|
+
function validateImplementation(directory,generation,implementation,options={}){
|
|
9
|
+
assert.equal(implementation.schemaVersion,1,'Unsupported implementation mapping');assert.ok(Array.isArray(implementation.bindings),'Implementation bindings required');
|
|
10
|
+
exact(implementation.bindings.map(item=>item.bindingId),generation.bindings.map(item=>item.id),'Implementation binding coverage');
|
|
11
|
+
const map=options.frameworkMap||framework();let pending=0;
|
|
12
|
+
for(const binding of generation.bindings){
|
|
13
|
+
const item=implementation.bindings.find(item=>item.bindingId===binding.id);
|
|
14
|
+
assert.ok(map.capabilities[binding.capabilityId]?.components?.length,`Framework capability mapping missing: ${binding.capabilityId}`);
|
|
15
|
+
assert.ok(['pending','implemented'].includes(item.status),'Unknown implementation status');
|
|
16
|
+
assert.ok(Array.isArray(item.scenarioIds),'Implementation scenario IDs required');exact(item.scenarioIds,generation.scenarios.filter(s=>s.bindingId===binding.id).map(s=>s.id),'Implementation scenario coverage');
|
|
17
|
+
if(item.status==='pending'){pending++;continue;}
|
|
18
|
+
assert.ok(Array.isArray(item.components)&&item.components.length,'Implemented binding needs components');
|
|
19
|
+
for(const component of item.components)assert.ok(map.capabilities[binding.capabilityId].components.includes(component)||/^application:[A-Za-z][\w.-]*$/.test(component),`Undeclared framework component: ${component}`);
|
|
20
|
+
assert.ok(Array.isArray(item.files)&&item.files.length,'Implemented binding needs source files');
|
|
21
|
+
for(const source of item.files){
|
|
22
|
+
assert.ok(typeof source.path==='string'&&/^(?:app\/src\/|app\/public\/|mocks\/)/.test(source.path),'Implementation must reference actual application source');
|
|
23
|
+
nonempty(source.locator,'Implementation source locator');const file=inside(directory,source.path);assert.ok(fs.statSync(file).isFile(),'Implementation source must be a file');
|
|
24
|
+
assert.ok(fs.readFileSync(file,'utf8').includes(source.locator),`Implementation locator absent from source: ${source.path}`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
if(options.complete)assert.equal(pending,0,'Implementation pending; status alone cannot pass acceptance');
|
|
28
|
+
return {status:pending?'pending':'implemented',bindings:implementation.bindings.length,pending,invocationVerified:false};
|
|
29
|
+
}
|
|
30
|
+
function validateObservations(directory,generation,report){
|
|
31
|
+
const observations=report.generationObservations;assert.ok(Array.isArray(observations),'Generation observations required');
|
|
32
|
+
exact(observations.map(item=>item.scenarioId),generation.scenarios.map(item=>item.id),'Generation observation scenario coverage');
|
|
33
|
+
for(const scenario of generation.scenarios){
|
|
34
|
+
const item=observations.find(item=>item.scenarioId===scenario.id);assert.equal(item.bindingId,scenario.bindingId,'Observation binding differs');assert.equal(item.status,'passed','Generation observation must pass');
|
|
35
|
+
assert.ok(Array.isArray(item.steps)&&item.steps.length,'Actual generation observation steps required');
|
|
36
|
+
for(const step of item.steps)for(const key of ['action','target','observed'])nonempty(step[key],`Observation step ${key}`);
|
|
37
|
+
assert.ok(Array.isArray(item.toolCallIds)&&item.toolCallIds.length,'Observation needs actual host tool call locators');for(const call of item.toolCallIds)nonempty(call,'Tool call ID');
|
|
38
|
+
assert.ok(Array.isArray(item.evidence)&&item.evidence.length,'Observation evidence files required');
|
|
39
|
+
for(const evidence of item.evidence){assert.ok(typeof evidence.path==='string'&&evidence.path.startsWith('qa/'),'Observation evidence must be inside QA');const bytes=fs.readFileSync(inside(directory,evidence.path));assert.ok(bytes.length,'Observation evidence is empty');assert.equal(hash(bytes),evidence.sha256,'Observation evidence bytes changed');}
|
|
40
|
+
}
|
|
41
|
+
return {status:'passed',scenarios:observations.length,invocationVerified:false};
|
|
42
|
+
}
|
|
43
|
+
function designSummary(generation,previous=''){
|
|
44
|
+
const start='<!-- generation-choices:start -->',end='<!-- generation-choices:end -->';
|
|
45
|
+
const block=`${start}\n客户固定选择:布局 ${generation.presentation.layout};风格 ${generation.presentation.style};密度 ${generation.presentation.density}。四主题默认明亮;覆盖外壳、组件与浮层。未决能力 ${generation.pendingDecisions.length} 项;未决参考功能节点 ${generation.pendingReferenceElements.length} 项。当前依据为 inputs/generation.json。\n${end}`;
|
|
46
|
+
const first=previous.indexOf(start),last=previous.indexOf(end);
|
|
47
|
+
assert.ok((first===-1&&last===-1)||(first>=0&&last>first&&previous.indexOf(start,first+1)===-1&&previous.indexOf(end,last+1)===-1),'Ambiguous generation design markers; preserve the existing design');
|
|
48
|
+
return first===-1?`${previous.trimEnd()}\n\n${block}\n`:`${previous.slice(0,first)}${block}${previous.slice(last+end.length)}`;
|
|
49
|
+
}
|
|
50
|
+
module.exports={framework,initialize,validateImplementation,validateObservations,designSummary};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const {fs,path,assert,json,inside,write}=require('./native-files.cjs');
|
|
3
|
+
function run(directory,options,lib){
|
|
4
|
+
const meta=json(inside(directory,'prototype.json')),old=json(inside(directory,'inputs/scope.json'));
|
|
5
|
+
assert.equal(meta.runtime.generationContract,1,'Replan requires an existing generation contract; legacy inputs are not implicitly upgraded');assert.equal(old.schemaVersion,3);
|
|
6
|
+
const scope=json(path.resolve(options.scope));assert.equal(scope.schemaVersion,3,'Replan cannot downgrade the fixed generation contract');
|
|
7
|
+
const template=json(inside(directory,'inputs/template-snapshot.json')),fixed=json(inside(directory,'inputs/generation.json'));
|
|
8
|
+
const generation=require('./generation-contract.cjs').compile(template,scope,{definition:fixed.definition}),templates=require('./template-contract.cjs');
|
|
9
|
+
const selection=require('./content-template.cjs').scopeInputs(template,scope,templates);
|
|
10
|
+
const checked=templates.checkCapabilities({...selection.fixed,scope});assert.equal(checked.status,'passed',checked.errors?.join('; '));
|
|
11
|
+
const scenarioDetails=[...checked.requiredScenarios,...generation.scenarios];const previous=json(inside(directory,'inputs/acceptance.json'));
|
|
12
|
+
const drawer=generation.bindings.some(binding=>binding.capabilityId==='dialog'&&binding.parameters.presentation==='drawer');
|
|
13
|
+
const acceptance={...previous,requiredScenarios:scenarioDetails.map(item=>typeof item==='string'?item:item.id),scenarioDetails,frameworkScenarios:['framework-responsive','framework-instance-isolation','template-content-projection',...(drawer?['framework-drawer-motion','framework-mask-blur','framework-focus-scroll']:[])]};
|
|
14
|
+
const config=json(inside(directory,'app/public/prototype-config.json')),theme=selection.fixed.bindings.find(item=>item.capabilityId==='theme');
|
|
15
|
+
const changes={
|
|
16
|
+
'inputs/scope.json':scope,'inputs/generation.json':generation,'inputs/capabilities.json':selection.fixed,'inputs/acceptance.json':acceptance,
|
|
17
|
+
'requirements-map.json':{schemaVersion:2,requirements:scope.requirements.map(r=>({...r,scenarios:scenarioDetails.filter(s=>s.bindingId.startsWith('app-shell/')||r.pages.some(page=>s.bindingId.startsWith(page+'/'))).map(s=>s.id)})),bindings:[...selection.fixed.bindings,...generation.bindings]},
|
|
18
|
+
'app/public/prototype-config.json':{...config,pages:scope.pages.map(page=>({id:page.id,title:page.title})),presentation:generation.presentation,navigation:generation.navigation,generation:{pages:generation.pages},theme:templates.resolveThemeParameters(theme.parameters)},
|
|
19
|
+
'design-system/implementation.json':require('./generation-evidence.cjs').initialize(generation),
|
|
20
|
+
'design-system/MASTER.md':require('./generation-evidence.cjs').designSummary(generation,fs.readFileSync(inside(directory,'design-system/MASTER.md'),'utf8')),
|
|
21
|
+
'mocks/contract.json':{...json(inside(directory,'mocks/contract.json')),status:'needs-business-data',requirements:scope.requirements.map(item=>item.id)},
|
|
22
|
+
'prototype.json':{...meta,business:{...meta.business,status:'needs-adaptation'}}
|
|
23
|
+
};
|
|
24
|
+
// Retain implementation locations for review, but require re-attestation after input changes.
|
|
25
|
+
const implemented=json(inside(directory,'design-system/implementation.json'));
|
|
26
|
+
for(const item of changes['design-system/implementation.json'].bindings){const prior=implemented.bindings.find(entry=>entry.bindingId===item.bindingId);if(prior){item.components=prior.components;item.files=prior.files;}}
|
|
27
|
+
const backup=new Map(Object.keys(changes).map(file=>[file,fs.readFileSync(inside(directory,file))]));
|
|
28
|
+
try{
|
|
29
|
+
for(const [file,value]of Object.entries(changes))write(directory,file,value);
|
|
30
|
+
const current=lib.manifest(directory);
|
|
31
|
+
return lib.revise(directory,{owner:options.owner,revision:options.revision,expectedRevision:current.revision,expectedDigest:current.digest,acceptInputs:true});
|
|
32
|
+
}catch(error){for(const [file,bytes]of backup)fs.writeFileSync(inside(directory,file),bytes);throw error;}
|
|
33
|
+
}
|
|
34
|
+
module.exports={run};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
const fs=require('node:fs'),path=require('node:path');
|
|
4
|
+
function main(argv=process.argv.slice(2),raw=fs.readFileSync(0,'utf8')) {
|
|
5
|
+
if(Buffer.byteLength(raw)>4*1024*1024)throw new Error('Hook input too large');
|
|
6
|
+
const options={};while(argv.length){const key=argv.shift();if(!key.startsWith('--')||!argv.length)throw new Error('Invalid Hook arguments');options[key.slice(2)]=argv.shift();}
|
|
7
|
+
const {OWNER,EVENTS}=require('../codebuddy-registration.cjs');
|
|
8
|
+
if(options.registration!==OWNER||!path.isAbsolute(options.base||'')||!EVENTS.includes(options.event)||!['ide','cli'].includes(options.product))throw new Error('Invalid Hook registration');
|
|
9
|
+
const result=require('../codebuddy-hook.cjs').handle(JSON.parse(raw),options);
|
|
10
|
+
if(result.deny)return {code:0,output:{hookSpecificOutput:{hookEventName:'PreToolUse',permissionDecision:'deny',permissionDecisionReason:result.reason}}};
|
|
11
|
+
if(result.feedback)return {code:2,output:{decision:'block',reason:result.reason}};
|
|
12
|
+
return {code:0,output:{continue:true}};
|
|
13
|
+
}
|
|
14
|
+
if(require.main===module)try{const result=main();process.stdout.write(JSON.stringify(result.output)+'\n');if(result.code===2)process.stderr.write(result.output.reason+'\n');process.exitCode=result.code;}
|
|
15
|
+
catch(error){process.stderr.write(`i3 prototype audit unavailable: ${error.message}. Strict A gates remain unverified.\n`);process.exitCode=1;}
|
|
16
|
+
module.exports={main};
|
|
@@ -5,7 +5,7 @@ try {
|
|
|
5
5
|
const { options, positional } = lib.parseArgs(process.argv.slice(2));
|
|
6
6
|
const [operation, directory] = positional;
|
|
7
7
|
if (!directory || !['inspect', 'manifest', 'revise', 'confirm-baseline'].includes(operation)) throw new Error('Usage: manage-baseline.cjs <inspect|manifest|revise|confirm-baseline> <directory> [--revision r002] [--confirmation file.json]');
|
|
8
|
-
const result = operation === 'manifest' ? lib.contentManifest(directory) : operation === 'inspect' ? lib.compareBaseline(directory) : operation === 'revise' ? lib.revisePrototype(directory, options.revision) : lib.freezeBaseline(directory, JSON.parse(fs.readFileSync(options.confirmation, 'utf8')));
|
|
8
|
+
const result = operation === 'manifest' ? lib.contentManifest(directory) : operation === 'inspect' ? lib.compareBaseline(directory) : operation === 'revise' ? lib.revisePrototype(directory, options.revision, options) : lib.freezeBaseline(directory, JSON.parse(fs.readFileSync(options.confirmation, 'utf8')));
|
|
9
9
|
process.stdout.write(JSON.stringify(result, null, 2) + '\n');
|
|
10
10
|
if (result.status === 'corrupt-baseline') process.exitCode = 1;
|
|
11
11
|
} catch (error) { process.stderr.write(error.message + '\n'); process.exitCode = 1; }
|