project-tiny-context-harness 0.7.8 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +393 -375
- package/assets/README.md +567 -552
- package/assets/README.zh-CN.md +339 -321
- package/assets/agents/.gitkeep +1 -1
- package/assets/agents/AGENTS_CORE.md +66 -58
- package/assets/context_templates/architecture.md +33 -33
- package/assets/context_templates/area.md +39 -39
- package/assets/context_templates/context.toml +30 -30
- package/assets/context_templates/deployment.md +35 -35
- package/assets/context_templates/global.md +52 -51
- package/assets/context_templates/product-surface-contract.md +70 -70
- package/assets/context_templates/screen-contract.md +186 -177
- package/assets/context_templates/verification.md +32 -32
- package/assets/github/.gitkeep +1 -1
- package/assets/github/harness.yml +41 -41
- package/assets/make/.gitkeep +1 -1
- package/assets/make/ty-context.mk +48 -48
- package/assets/skills/context_development_engineer/SKILL.md +135 -112
- package/assets/skills/context_full_project_export/SKILL.md +70 -70
- package/assets/skills/context_harness_upgrade/SKILL.md +60 -60
- package/assets/skills/context_product_plan/SKILL.md +88 -87
- package/assets/skills/context_surface_contract/SKILL.md +191 -191
- package/assets/skills/context_uiux_design/SKILL.md +158 -154
- package/assets/skills/design-resource-authoring/SKILL.md +84 -79
- package/assets/skills/design-resource-authoring/references/downstream-handoff.md +136 -108
- package/assets/skills/design-resource-authoring/references/open-design-provider.md +133 -127
- package/assets/skills/design-resource-authoring/references/resource-selection.md +173 -173
- package/assets/skills/design-system-authoring/SKILL.md +57 -57
- package/assets/skills/design-system-authoring/agents/openai.yaml +6 -6
- package/assets/skills/design-system-authoring/references/authority-adoption.md +48 -47
- package/assets/skills/design-system-authoring/references/open-design-design-system-provider.md +110 -110
- package/assets/skills/long-task-workflow/SKILL.md +100 -87
- package/assets/skills/long-task-workflow/agents/openai.yaml +4 -4
- package/assets/skills/long-task-workflow/references/authority-lifecycle.md +76 -59
- package/assets/skills/long-task-workflow/references/contract-authoring.md +116 -103
- package/assets/skills/long-task-workflow/references/evidence-design.md +78 -75
- package/assets/skills/long-task-workflow/references/source-authoring.md +101 -98
- package/assets/skills/normal-long-task/SKILL.md +12 -12
- package/assets/skills/source-plan-authoring/SKILL.md +14 -14
- package/assets/tools/validate_context.py +442 -442
- package/dist/commands/design-resource.d.ts +1 -0
- package/dist/commands/design-resource.js +32 -0
- package/dist/commands/index.js +4 -0
- package/dist/commands/long-task-command-args.d.ts +1 -0
- package/dist/commands/long-task-command-args.js +6 -0
- package/dist/commands/long-task-revision.js +16 -4
- package/dist/commands/long-task.js +12 -5
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/lib/design-md.js +3 -1
- package/dist/lib/design-resource-handoff-file-primitives.d.ts +4 -0
- package/dist/lib/design-resource-handoff-file-primitives.js +14 -0
- package/dist/lib/design-resource-handoff-file-validation.d.ts +2 -0
- package/dist/lib/design-resource-handoff-file-validation.js +142 -0
- package/dist/lib/design-resource-handoff-parser.d.ts +3 -0
- package/dist/lib/design-resource-handoff-parser.js +29 -0
- package/dist/lib/design-resource-handoff-policy.d.ts +3 -0
- package/dist/lib/design-resource-handoff-policy.js +36 -0
- package/dist/lib/design-resource-handoff-shape-evidence.d.ts +4 -0
- package/dist/lib/design-resource-handoff-shape-evidence.js +84 -0
- package/dist/lib/design-resource-handoff-shape-primitives.d.ts +9 -0
- package/dist/lib/design-resource-handoff-shape-primitives.js +44 -0
- package/dist/lib/design-resource-handoff-shape-structure.d.ts +5 -0
- package/dist/lib/design-resource-handoff-shape-structure.js +118 -0
- package/dist/lib/design-resource-handoff-shape.d.ts +2 -0
- package/dist/lib/design-resource-handoff-shape.js +75 -0
- package/dist/lib/design-resource-handoff-types.d.ts +139 -0
- package/dist/lib/design-resource-handoff-types.js +46 -0
- package/dist/lib/design-resource-handoff-validation-coverage.d.ts +4 -0
- package/dist/lib/design-resource-handoff-validation-coverage.js +168 -0
- package/dist/lib/design-resource-handoff-validation-primitives.d.ts +11 -0
- package/dist/lib/design-resource-handoff-validation-primitives.js +26 -0
- package/dist/lib/design-resource-handoff-validation-structure.d.ts +6 -0
- package/dist/lib/design-resource-handoff-validation-structure.js +93 -0
- package/dist/lib/design-resource-handoff-validation.d.ts +3 -0
- package/dist/lib/design-resource-handoff-validation.js +78 -0
- package/dist/lib/design-resource-handoff-web-dependency-validation.d.ts +3 -0
- package/dist/lib/design-resource-handoff-web-dependency-validation.js +70 -0
- package/dist/lib/long-task-activation-validation.js +14 -2
- package/dist/lib/long-task-authoring-preflight-diagnostics.js +7 -0
- package/dist/lib/long-task-authoring-preflight.js +39 -1
- package/dist/lib/long-task-authority-material-diff.js +14 -0
- package/dist/lib/long-task-authority-materials.js +21 -0
- package/dist/lib/long-task-authority-policy.d.ts +33 -0
- package/dist/lib/long-task-authority-policy.js +36 -0
- package/dist/lib/long-task-authority-revision-analysis.d.ts +1 -0
- package/dist/lib/long-task-authority-revision-analysis.js +30 -0
- package/dist/lib/long-task-authority-revision-brief.d.ts +3 -0
- package/dist/lib/long-task-authority-revision-brief.js +74 -0
- package/dist/lib/long-task-authority-revision-details.js +16 -16
- package/dist/lib/long-task-authority-revision-diagnosis.d.ts +1 -1
- package/dist/lib/long-task-authority-revision-diagnosis.js +8 -0
- package/dist/lib/long-task-authority-revision-enforcement.js +7 -3
- package/dist/lib/long-task-authority-revision-summary.d.ts +3 -0
- package/dist/lib/long-task-authority-revision-summary.js +133 -21
- package/dist/lib/long-task-authority-revision-types.d.ts +19 -1
- package/dist/lib/long-task-authority-revision.js +5 -0
- package/dist/lib/long-task-authority-types.d.ts +2 -0
- package/dist/lib/long-task-authority.js +10 -2
- package/dist/lib/long-task-check-execution-policy.js +2 -0
- package/dist/lib/long-task-claim-definitions.js +1 -5
- package/dist/lib/long-task-contract-types.d.ts +2 -0
- package/dist/lib/long-task-counterfactual-sandbox.d.ts +7 -0
- package/dist/lib/long-task-counterfactual-sandbox.js +27 -1
- package/dist/lib/long-task-delivery-compiler.js +26 -8
- package/dist/lib/long-task-delivery-types.d.ts +1 -0
- package/dist/lib/long-task-delivery-types.js +1 -0
- package/dist/lib/long-task-delivery-validation.js +3 -0
- package/dist/lib/long-task-design-resource-handoff.d.ts +2 -0
- package/dist/lib/long-task-design-resource-handoff.js +177 -0
- package/dist/lib/long-task-evidence-capability-codec.js +19 -0
- package/dist/lib/long-task-evidence-capability-policy.js +4 -0
- package/dist/lib/long-task-evidence-capability-runtime.js +25 -0
- package/dist/lib/long-task-evidence-capability-types.d.ts +9 -1
- package/dist/lib/long-task-outcome-parser.js +11 -1
- package/dist/lib/long-task-paths.d.ts +0 -1
- package/dist/lib/long-task-paths.js +0 -4
- package/dist/lib/long-task-playwright-case-evidence.d.ts +43 -0
- package/dist/lib/long-task-playwright-case-evidence.js +152 -0
- package/dist/lib/long-task-playwright-evidence.js +19 -152
- package/dist/lib/long-task-runner-freeze.d.ts +2 -2
- package/dist/lib/long-task-runner-freeze.js +2 -1
- package/dist/lib/long-task-runtime-types.d.ts +2 -0
- package/dist/lib/long-task-semantic-contract-types.d.ts +1 -1
- package/dist/lib/long-task-semantic-drift-migration.js +6 -1
- package/dist/lib/long-task-shape-primitives.d.ts +1 -1
- package/dist/lib/long-task-shape-primitives.js +1 -0
- package/dist/lib/long-task-status-v2.js +12 -7
- package/dist/lib/long-task-ui-design-policy.d.ts +17 -0
- package/dist/lib/long-task-ui-design-policy.js +128 -0
- package/dist/lib/long-task-ui-surface-policy.d.ts +4 -0
- package/dist/lib/long-task-ui-surface-policy.js +109 -0
- package/dist/lib/long-task-ui-surface-shape.d.ts +2 -0
- package/dist/lib/long-task-ui-surface-shape.js +94 -0
- package/dist/lib/long-task-ui-surface-types.d.ts +44 -0
- package/dist/lib/long-task-ui-surface-types.js +1 -0
- package/dist/lib/long-task-ui-surface-validation.d.ts +11 -0
- package/dist/lib/long-task-ui-surface-validation.js +62 -0
- package/dist/lib/long-task-verification-preview.d.ts +55 -0
- package/dist/lib/long-task-verification-preview.js +120 -0
- package/dist/lib/long-task-verifier-v2.js +11 -18
- package/dist/lib/long-task-workspace-scope.d.ts +38 -0
- package/dist/lib/long-task-workspace-scope.js +121 -0
- package/dist/lib/long-task-workspace.d.ts +1 -0
- package/dist/lib/long-task-workspace.js +73 -9
- package/dist/schemas/long-task-delivery-v2/long-task-delivery-v2.schema.json +84 -2
- package/migrations/README.md +7 -7
- package/package.json +3 -3
- package/source-mappings.yaml +25 -25
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function designResource(args: string[]): Promise<void>;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { preflightDesignResourceHandoff } from "../lib/design-resource-handoff-validation.js";
|
|
2
|
+
import { normalizeRepositoryFile } from "../lib/long-task-paths.js";
|
|
3
|
+
import { canonicalJson } from "../lib/strict-codec.js";
|
|
4
|
+
export async function designResource(args) {
|
|
5
|
+
const [subcommand = "help", ...rest] = args;
|
|
6
|
+
if (subcommand === "help") {
|
|
7
|
+
help();
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
if (subcommand !== "preflight")
|
|
11
|
+
throw new Error(`Unknown design-resource subcommand: ${subcommand}`);
|
|
12
|
+
const json = rest.includes("--json");
|
|
13
|
+
const positional = rest.filter((item) => item !== "--json");
|
|
14
|
+
if (positional.length !== 1)
|
|
15
|
+
throw new Error("usage: ty-context design-resource preflight <handoff.md> [--json]");
|
|
16
|
+
const handoffPath = normalizeRepositoryFile(positional[0], "design_resource_handoff");
|
|
17
|
+
const result = await preflightDesignResourceHandoff(process.cwd(), handoffPath);
|
|
18
|
+
if (json) {
|
|
19
|
+
process.stdout.write(canonicalJson(result));
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
console.log(`Design resource handoff ready: ${result.handoff_path}`);
|
|
23
|
+
console.log(`Scope: ${result.handoff.scope.key}`);
|
|
24
|
+
console.log(`Targets: ${result.handoff.targets.map((item) => item.key).join(", ")}`);
|
|
25
|
+
console.log(`Coverage: ${result.counts.subjects} subjects x 8 dimensions (${result.counts.coverage} grouped rows)`);
|
|
26
|
+
console.log(`Acceptance blockers: ${result.counts.acceptance_blockers}`);
|
|
27
|
+
}
|
|
28
|
+
function help() {
|
|
29
|
+
console.log(`ty-context design-resource commands:
|
|
30
|
+
preflight <handoff.md> [--json]
|
|
31
|
+
Validate one selected implementation handoff`);
|
|
32
|
+
}
|
package/dist/commands/index.js
CHANGED
|
@@ -3,6 +3,7 @@ import { compositeLongTask } from "./composite-long-task.js";
|
|
|
3
3
|
import { compositeCampaign } from "./composite-campaign.js";
|
|
4
4
|
import { longTask } from "./long-task.js";
|
|
5
5
|
import { deliverySet } from "./delivery-set.js";
|
|
6
|
+
import { designResource } from "./design-resource.js";
|
|
6
7
|
import { doctor } from "./doctor.js";
|
|
7
8
|
import { exportContext } from "./export-context.js";
|
|
8
9
|
import { enable } from "./enable.js";
|
|
@@ -28,6 +29,7 @@ export const commands = {
|
|
|
28
29
|
"validate-code-modularity": (args) => validate(["validate-code-modularity", ...args]),
|
|
29
30
|
"validate-harness": (args) => validate(["validate-harness", ...args]),
|
|
30
31
|
"long-task": (args) => withLongTaskProfile(args, longTask),
|
|
32
|
+
"design-resource": designResource,
|
|
31
33
|
"delivery-set": deliverySet,
|
|
32
34
|
"composite-long-task": compositeLongTask,
|
|
33
35
|
"composite-campaign": compositeCampaign,
|
|
@@ -52,6 +54,8 @@ export function help() {
|
|
|
52
54
|
validate-code-modularity
|
|
53
55
|
Enforce touched handwritten source file modularity
|
|
54
56
|
validate-harness Run validate-context and validate-code-modularity
|
|
57
|
+
design-resource <subcommand>
|
|
58
|
+
Validate a selected design-resource implementation handoff
|
|
55
59
|
long-task <subcommand>
|
|
56
60
|
Manage one Canonical Delivery Contract in the current workspace
|
|
57
61
|
delivery-set <subcommand>
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
export declare function option(args: string[], name: string): string | undefined;
|
|
2
|
+
export declare function flag(args: string[], name: string): boolean;
|
|
2
3
|
export declare function rejectOptions(args: string[], allowed: string[]): void;
|
|
3
4
|
export declare function rejectUnknown(actual: string[], allowed: string[]): void;
|
|
@@ -9,6 +9,12 @@ export function option(args, name) {
|
|
|
9
9
|
throw new Error(`${name} requires a value`);
|
|
10
10
|
return value;
|
|
11
11
|
}
|
|
12
|
+
export function flag(args, name) {
|
|
13
|
+
const count = args.filter((value) => value === name).length;
|
|
14
|
+
if (count > 1)
|
|
15
|
+
throw new Error(`duplicate option: ${name}`);
|
|
16
|
+
return count === 1;
|
|
17
|
+
}
|
|
12
18
|
export function rejectOptions(args, allowed) {
|
|
13
19
|
for (let index = 0; index < args.length; index += 2)
|
|
14
20
|
if (!allowed.includes(args[index]) || !args[index + 1])
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { diagnoseAuthorityRevision } from "../lib/long-task-authority-revision-diagnosis.js";
|
|
3
|
+
import { authorityRevisionDecisionNextAction } from "../lib/long-task-authority-revision-brief.js";
|
|
3
4
|
import { projectAuthorityRevisionDecision } from "../lib/long-task-authority-revision-summary.js";
|
|
4
5
|
import { canRetainProgressForSupportingContextRevision } from "../lib/long-task-context-authority.js";
|
|
5
6
|
import { compileDeliveryContract } from "../lib/long-task-delivery-compiler.js";
|
|
@@ -89,7 +90,7 @@ function printCompileResult(compiled, previous, preserveProgress, revisionPropos
|
|
|
89
90
|
? projectAuthorityRevisionDecision(revisionProposal)
|
|
90
91
|
: null,
|
|
91
92
|
next_action: firstAuthorityLock
|
|
92
|
-
? "
|
|
93
|
+
? "End this turn now and ask the user to choose continue_current_model or switch_model_then_resume. Do not implement, edit files, build, or test until an explicit task-specific model strategy is received; generic continue or resume language does not satisfy this checkpoint."
|
|
93
94
|
: authorityChanged
|
|
94
95
|
? "Run status or resume, then continue rolling implementation or repair under the adopted Authority Revision."
|
|
95
96
|
: "Continue rolling implementation or repair under the active Authority.",
|
|
@@ -116,14 +117,15 @@ async function printPendingDecision(workdir, previous) {
|
|
|
116
117
|
const pending = await readPendingAuthorityRevision(workdir);
|
|
117
118
|
if (!pending)
|
|
118
119
|
throw new Error("authority_revision_pending_state_missing");
|
|
120
|
+
const decision = projectAuthorityRevisionDecision(pending);
|
|
119
121
|
console.log(JSON.stringify({
|
|
120
122
|
status: "authority_revision_pending",
|
|
121
123
|
acceptance_authorized: false,
|
|
122
124
|
delivery_completed_by_this_event: false,
|
|
123
125
|
native_goal_effect: "none",
|
|
124
126
|
active_compiled_identity: previous?.compiled_identity ?? null,
|
|
125
|
-
pending_authority_revision:
|
|
126
|
-
next_action:
|
|
127
|
+
pending_authority_revision: decision,
|
|
128
|
+
next_action: authorityRevisionDecisionNextAction(decision),
|
|
127
129
|
}));
|
|
128
130
|
}
|
|
129
131
|
async function diagnoseRevision(workdir, args) {
|
|
@@ -156,7 +158,17 @@ function executionModelCheckpoint(firstAuthorityLock) {
|
|
|
156
158
|
required: true,
|
|
157
159
|
phase: "post_authority_lock_pre_implementation",
|
|
158
160
|
options: ["continue_current_model", "switch_model_then_resume"],
|
|
159
|
-
|
|
161
|
+
turn_boundary: "end_current_turn",
|
|
162
|
+
blocked_until_explicit_choice: [
|
|
163
|
+
"product_implementation",
|
|
164
|
+
"file_edits",
|
|
165
|
+
"build",
|
|
166
|
+
"test_execution",
|
|
167
|
+
],
|
|
168
|
+
explicit_task_specific_choice_required: true,
|
|
169
|
+
prior_explicit_task_specific_choice_satisfies: true,
|
|
170
|
+
generic_continue_satisfies: false,
|
|
171
|
+
message: "Authority Lock created. End the current turn before product implementation, file edits, builds, or tests. Ask the user to explicitly choose continue_current_model or switch_model_then_resume. Generic continue, resume, finish, or continue-goal language does not satisfy this checkpoint.",
|
|
160
172
|
};
|
|
161
173
|
}
|
|
162
174
|
function message(error) {
|
|
@@ -5,9 +5,10 @@ import { abandonLongTaskState, forceClearCorruptActiveState, } from "../lib/long
|
|
|
5
5
|
import { verifyDeliveryContract } from "../lib/long-task-verifier-v2.js";
|
|
6
6
|
import { repositoryRoot } from "../lib/long-task-workspace.js";
|
|
7
7
|
import { initializeLongTask, preflightLongTask, } from "./long-task-authoring.js";
|
|
8
|
-
import { option, rejectOptions, rejectUnknown, } from "./long-task-command-args.js";
|
|
8
|
+
import { flag, option, rejectOptions, rejectUnknown, } from "./long-task-command-args.js";
|
|
9
9
|
import { explainLongTask } from "./long-task-explain.js";
|
|
10
10
|
import { handleLongTaskRevisionCommand } from "./long-task-revision.js";
|
|
11
|
+
import { previewVerificationExecution } from "../lib/long-task-verification-preview.js";
|
|
11
12
|
export async function longTask(args) {
|
|
12
13
|
const subcommand = args[0] ?? "help";
|
|
13
14
|
if (subcommand === "help")
|
|
@@ -96,9 +97,15 @@ export async function longTask(args) {
|
|
|
96
97
|
throw new Error(`Unknown long-task subcommand: ${subcommand}`);
|
|
97
98
|
}
|
|
98
99
|
async function verify(workdir, args) {
|
|
99
|
-
const
|
|
100
|
-
const
|
|
101
|
-
|
|
100
|
+
const explain = flag(args, "--explain");
|
|
101
|
+
const optionArgs = args.filter((value) => value !== "--explain");
|
|
102
|
+
const outcome = option(optionArgs, "--outcome");
|
|
103
|
+
const check = option(optionArgs, "--check");
|
|
104
|
+
rejectOptions(optionArgs, ["--outcome", "--check"]);
|
|
105
|
+
if (explain) {
|
|
106
|
+
console.log(JSON.stringify(await previewVerificationExecution(workdir, { outcome, check })));
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
102
109
|
const result = await verifyDeliveryContract(workdir, { outcome, check });
|
|
103
110
|
console.log(JSON.stringify(result));
|
|
104
111
|
if (result.findings.length)
|
|
@@ -125,7 +132,7 @@ function help() {
|
|
|
125
132
|
diagnose-revision <workdir> [--outcome <key>] [--check <key>]
|
|
126
133
|
approve-authority-revision <workdir> --revision <sha>
|
|
127
134
|
explain <workdir>
|
|
128
|
-
verify <workdir> [--outcome <key>] [--check <key>]
|
|
135
|
+
verify <workdir> [--outcome <key>] [--check <key>] [--explain]
|
|
129
136
|
status <workdir>
|
|
130
137
|
resume <workdir>
|
|
131
138
|
doctor <workdir>
|
package/dist/index.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ export { commands } from "./commands/index.js";
|
|
|
2
2
|
export { compileDeliveryContract, type CompileDeliveryOptionsV2, } from "./lib/long-task-delivery-compiler.js";
|
|
3
3
|
export { DELIVERY_CONTRACT_FILE, parseDeliveryContract, parseDeliveryContractBundle, } from "./lib/long-task-delivery-parser.js";
|
|
4
4
|
export { evaluateContractBoundary } from "./lib/long-task-boundary-check.js";
|
|
5
|
+
export { preflightDesignResourceHandoff } from "./lib/design-resource-handoff-validation.js";
|
|
5
6
|
export { classifyLongTaskRisk, validateRiskProof, } from "./lib/long-task-risk.js";
|
|
6
7
|
export { verifyDeliveryContract } from "./lib/long-task-verifier-v2.js";
|
|
7
8
|
export { runDeliveryFinalGate } from "./lib/long-task-final-v2.js";
|
package/dist/index.js
CHANGED
|
@@ -2,6 +2,7 @@ export { commands } from "./commands/index.js";
|
|
|
2
2
|
export { compileDeliveryContract, } from "./lib/long-task-delivery-compiler.js";
|
|
3
3
|
export { DELIVERY_CONTRACT_FILE, parseDeliveryContract, parseDeliveryContractBundle, } from "./lib/long-task-delivery-parser.js";
|
|
4
4
|
export { evaluateContractBoundary } from "./lib/long-task-boundary-check.js";
|
|
5
|
+
export { preflightDesignResourceHandoff } from "./lib/design-resource-handoff-validation.js";
|
|
5
6
|
export { classifyLongTaskRisk, validateRiskProof, } from "./lib/long-task-risk.js";
|
|
6
7
|
export { verifyDeliveryContract } from "./lib/long-task-verifier-v2.js";
|
|
7
8
|
export { runDeliveryFinalGate } from "./lib/long-task-final-v2.js";
|
package/dist/lib/design-md.js
CHANGED
|
@@ -125,7 +125,8 @@ function designMdTemplate() {
|
|
|
125
125
|
"- Authored exact-value token source: not selected.",
|
|
126
126
|
"- Generation direction and generated token targets: not selected.",
|
|
127
127
|
"- Durable design references: none selected.",
|
|
128
|
-
"- For each selected reference, record a stable id, surface/route/component,
|
|
128
|
+
"- For each selected reference, record a stable id, surface/route/component, `exact-target` / `constraint` / `inspiration` interpretation, viewport/theme/state coverage, a readable immutable adopted path/URI plus digest, and the editable upstream owner/locator/update route (or an explicit manual/external-update boundary).",
|
|
129
|
+
"- Relevant product/design/development work must open affected selected exact targets/constraints; an index entry alone is not resource consumption.",
|
|
129
130
|
"",
|
|
130
131
|
"## Colors",
|
|
131
132
|
"",
|
|
@@ -161,6 +162,7 @@ function designMdTemplate() {
|
|
|
161
162
|
"- When there is a scan target such as UI source, page files, build output or a local/remote URL, run `npx impeccable detect <target>` before finalizing design changes.",
|
|
162
163
|
"- Treat Impeccable findings as design-review signals: fix issues that affect clarity, consistency, accessibility or trust, and note when there is no suitable scan target.",
|
|
163
164
|
"- After design decisions stabilize, replace the unconfigured marker, index the selected targets and authored token source/generation direction, and record durable tokens, component rules and do/don't guidance.",
|
|
165
|
+
"- Change an adopted target through its recorded upstream route, create or approve a new immutable version/digest, and update its index entry; never overwrite the adopted baseline in place.",
|
|
164
166
|
"",
|
|
165
167
|
].join("\n");
|
|
166
168
|
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { ParsedDesignResourceHandoffV1 } from "./design-resource-handoff-types.js";
|
|
2
|
+
export type DesignResource = ParsedDesignResourceHandoffV1["handoff"]["resources"][number];
|
|
3
|
+
export declare function htmlAttributes(source: string): Map<string, string>;
|
|
4
|
+
export declare function isTextResource(resource: DesignResource): boolean;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export function htmlAttributes(source) {
|
|
2
|
+
const attributes = new Map();
|
|
3
|
+
for (const match of source.matchAll(/([A-Za-z_:][A-Za-z0-9_.:-]*)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'=<>`]+)))?/gu))
|
|
4
|
+
attributes.set(match[1].toLowerCase(), match[2] ?? match[3] ?? match[4] ?? "");
|
|
5
|
+
return attributes;
|
|
6
|
+
}
|
|
7
|
+
export function isTextResource(resource) {
|
|
8
|
+
return (resource.media_type.startsWith("text/") ||
|
|
9
|
+
[
|
|
10
|
+
"application/javascript",
|
|
11
|
+
"application/json",
|
|
12
|
+
"application/xhtml+xml",
|
|
13
|
+
].includes(resource.media_type));
|
|
14
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { htmlAttributes, isTextResource, } from "./design-resource-handoff-file-primitives.js";
|
|
4
|
+
import { invalidDesignResourceHandoff } from "./design-resource-handoff-validation-primitives.js";
|
|
5
|
+
import { validateDesignResourceWebDependencyClosure } from "./design-resource-handoff-web-dependency-validation.js";
|
|
6
|
+
import { assertProtectedRepositoryFile } from "./long-task-protected-files.js";
|
|
7
|
+
import { markdownAnchors } from "./long-task-source-validation.js";
|
|
8
|
+
export async function validateDesignResourceFiles(repository, parsed) {
|
|
9
|
+
const resources = new Map(parsed.handoff.resources.map((resource) => [resource.key, resource]));
|
|
10
|
+
const contents = new Map();
|
|
11
|
+
for (const resource of parsed.handoff.resources) {
|
|
12
|
+
const file = await assertProtectedRepositoryFile(repository, path.resolve(repository, ...resource.path.split("/")), `design_resource:${resource.key}`);
|
|
13
|
+
contents.set(resource.key, await readFile(file));
|
|
14
|
+
}
|
|
15
|
+
for (const evidence of parsed.handoff.evidence)
|
|
16
|
+
validateLocator(evidence, resources.get(evidence.resource_ref), contents.get(evidence.resource_ref));
|
|
17
|
+
for (const target of parsed.handoff.targets)
|
|
18
|
+
if (target.source_profile.kind === "implementation_web")
|
|
19
|
+
validateDesignResourceWebDependencyClosure(target, resources, contents);
|
|
20
|
+
}
|
|
21
|
+
function validateLocator(evidence, resource, bytes) {
|
|
22
|
+
const { kind, value } = evidence.locator;
|
|
23
|
+
const label = `${evidence.key}:${resource.key}:${kind}:${value}`;
|
|
24
|
+
validateEvidenceResourceCompatibility(evidence, resource);
|
|
25
|
+
if (kind === "whole_resource") {
|
|
26
|
+
if (value !== ".")
|
|
27
|
+
invalidDesignResourceHandoff("locator_whole_resource_value", label);
|
|
28
|
+
if (resource.media_type.startsWith("text/") &&
|
|
29
|
+
!["asset", "motion_capture", "frame"].includes(evidence.kind))
|
|
30
|
+
invalidDesignResourceHandoff("locator_whole_resource_too_broad", label);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
const content = bytes.toString("utf8");
|
|
34
|
+
if (kind === "html_selector") {
|
|
35
|
+
requireMediaType(resource, ["text/html", "application/xhtml+xml"], label);
|
|
36
|
+
if (!htmlSelectorExists(content, value))
|
|
37
|
+
invalidDesignResourceHandoff("locator_not_found", label);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
if (kind === "markdown_anchor") {
|
|
41
|
+
requireMediaType(resource, ["text/markdown"], label);
|
|
42
|
+
if (value.startsWith("#") || !markdownAnchors(content).has(value))
|
|
43
|
+
invalidDesignResourceHandoff("locator_not_found", label);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
if (kind === "json_pointer") {
|
|
47
|
+
requireMediaType(resource, ["application/json"], label);
|
|
48
|
+
if (!jsonPointerExists(content, value))
|
|
49
|
+
invalidDesignResourceHandoff("locator_not_found", label);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
if (kind === "css_selector") {
|
|
53
|
+
requireMediaType(resource, ["text/css"], label);
|
|
54
|
+
if (!cssSelectorExists(content, value))
|
|
55
|
+
invalidDesignResourceHandoff("locator_not_found", label);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
requireMediaType(resource, ["text/css"], label);
|
|
59
|
+
if (!/^--[A-Za-z0-9_-]+$/u.test(value))
|
|
60
|
+
invalidDesignResourceHandoff("locator_css_custom_property_invalid", label);
|
|
61
|
+
const customProperty = new RegExp(`${escapeRegExp(value)}\\s*:`, "u");
|
|
62
|
+
if (!customProperty.test(content))
|
|
63
|
+
invalidDesignResourceHandoff("locator_not_found", label);
|
|
64
|
+
}
|
|
65
|
+
function validateEvidenceResourceCompatibility(evidence, resource) {
|
|
66
|
+
const machineReadableKinds = new Set([
|
|
67
|
+
"prototype_transition",
|
|
68
|
+
"motion_spec",
|
|
69
|
+
"responsive_spec",
|
|
70
|
+
"input_spec",
|
|
71
|
+
"accessibility_spec",
|
|
72
|
+
"semantic_tree",
|
|
73
|
+
"token_spec",
|
|
74
|
+
"annotation",
|
|
75
|
+
]);
|
|
76
|
+
if (machineReadableKinds.has(evidence.kind) && !isTextResource(resource))
|
|
77
|
+
invalidDesignResourceHandoff("evidence_resource_media_type_incompatible", `${evidence.key}:${evidence.kind}:${resource.media_type}`);
|
|
78
|
+
}
|
|
79
|
+
function requireMediaType(resource, expected, detail) {
|
|
80
|
+
if (!expected.includes(resource.media_type))
|
|
81
|
+
invalidDesignResourceHandoff("locator_media_type_incompatible", `${detail}:${resource.media_type}`);
|
|
82
|
+
}
|
|
83
|
+
function htmlSelectorExists(content, selector) {
|
|
84
|
+
const parsed = /^(?:([A-Za-z][A-Za-z0-9:-]*))?(?:#([A-Za-z][A-Za-z0-9_.:-]*))?(?:\[([A-Za-z_:][A-Za-z0-9_.:-]*)(?:=(?:"([^"]*)"|'([^']*)'|([^\]\s]+)))?\])?$/u.exec(selector);
|
|
85
|
+
if (!parsed || (!parsed[1] && !parsed[2] && !parsed[3]))
|
|
86
|
+
invalidDesignResourceHandoff("locator_html_selector_unsupported", selector);
|
|
87
|
+
const [, expectedTag, expectedId, expectedAttribute] = parsed;
|
|
88
|
+
const expectedValue = parsed[4] ?? parsed[5] ?? parsed[6];
|
|
89
|
+
for (const match of content.matchAll(/<([A-Za-z][A-Za-z0-9:-]*)\b([^<>]*)>/gu)) {
|
|
90
|
+
if (expectedTag && match[1].toLowerCase() !== expectedTag.toLowerCase())
|
|
91
|
+
continue;
|
|
92
|
+
const attributes = htmlAttributes(match[2]);
|
|
93
|
+
if (expectedId && attributes.get("id") !== expectedId)
|
|
94
|
+
continue;
|
|
95
|
+
if (expectedAttribute) {
|
|
96
|
+
const attributeName = expectedAttribute.toLowerCase();
|
|
97
|
+
if (!attributes.has(attributeName))
|
|
98
|
+
continue;
|
|
99
|
+
if (expectedValue !== undefined &&
|
|
100
|
+
attributes.get(attributeName) !== expectedValue)
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
function jsonPointerExists(content, pointer) {
|
|
108
|
+
if (!pointer.startsWith("/"))
|
|
109
|
+
return false;
|
|
110
|
+
let current;
|
|
111
|
+
try {
|
|
112
|
+
current = JSON.parse(content);
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
for (const token of pointer
|
|
118
|
+
.slice(1)
|
|
119
|
+
.split("/")
|
|
120
|
+
.map((part) => part.replace(/~1/gu, "/").replace(/~0/gu, "~"))) {
|
|
121
|
+
if (current === null ||
|
|
122
|
+
typeof current !== "object" ||
|
|
123
|
+
!Object.hasOwn(current, token))
|
|
124
|
+
return false;
|
|
125
|
+
current = current[token];
|
|
126
|
+
}
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
function cssSelectorExists(content, selector) {
|
|
130
|
+
if (!selector.trim() || selector.includes("{") || selector.includes("}"))
|
|
131
|
+
invalidDesignResourceHandoff("locator_css_selector_unsupported", selector);
|
|
132
|
+
for (const match of content.matchAll(/(?:^|\})\s*([^@{}][^{}]*)\{/gmu))
|
|
133
|
+
if (match[1]
|
|
134
|
+
.split(",")
|
|
135
|
+
.map((item) => item.trim())
|
|
136
|
+
.includes(selector))
|
|
137
|
+
return true;
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
function escapeRegExp(value) {
|
|
141
|
+
return value.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
|
|
142
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { ParsedDesignResourceHandoffV1 } from "./design-resource-handoff-types.js";
|
|
2
|
+
export declare function containsDesignResourceHandoff(content: string): boolean;
|
|
3
|
+
export declare function parseDesignResourceHandoffMarkdown(handoffPath: string, content: string): ParsedDesignResourceHandoffV1;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { parseDesignResourceHandoffShape } from "./design-resource-handoff-shape.js";
|
|
2
|
+
import { parseSourceItems } from "./long-task-source-item-parser.js";
|
|
3
|
+
import { parseStrictYaml } from "./strict-codec.js";
|
|
4
|
+
const FENCE = /^```yaml[ \t]+design-resource-handoff-v1[ \t]*\r?\n([\s\S]*?)^```[ \t]*$/gmu;
|
|
5
|
+
export function containsDesignResourceHandoff(content) {
|
|
6
|
+
FENCE.lastIndex = 0;
|
|
7
|
+
return FENCE.test(content);
|
|
8
|
+
}
|
|
9
|
+
export function parseDesignResourceHandoffMarkdown(handoffPath, content) {
|
|
10
|
+
FENCE.lastIndex = 0;
|
|
11
|
+
const blocks = [...content.matchAll(FENCE)];
|
|
12
|
+
if (blocks.length !== 1)
|
|
13
|
+
throw new Error(`design_resource_handoff_invalid:block_count:${handoffPath}:${blocks.length}`);
|
|
14
|
+
try {
|
|
15
|
+
const sourceItems = parseSourceItems(handoffPath, content);
|
|
16
|
+
return {
|
|
17
|
+
handoff_path: handoffPath,
|
|
18
|
+
handoff: parseDesignResourceHandoffShape(parseStrictYaml(blocks[0][1])),
|
|
19
|
+
source_item_keys: sourceItems.map((item) => item.key),
|
|
20
|
+
source_item_kinds: Object.fromEntries(sourceItems.map((item) => [item.key, item.kind])),
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
25
|
+
if (message.startsWith("design_resource_handoff_invalid:"))
|
|
26
|
+
throw error;
|
|
27
|
+
throw new Error(`design_resource_handoff_invalid:shape:${message}`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { DesignResourceDimension, DesignResourceEvidenceKind, DesignResourceVerificationMethod } from "./design-resource-handoff-types.js";
|
|
2
|
+
export declare const DESIGN_RESOURCE_EVIDENCE_BY_DIMENSION: Record<DesignResourceDimension, readonly DesignResourceEvidenceKind[]>;
|
|
3
|
+
export declare const DESIGN_RESOURCE_METHODS_BY_DIMENSION: Record<DesignResourceDimension, readonly DesignResourceVerificationMethod[]>;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export const DESIGN_RESOURCE_EVIDENCE_BY_DIMENSION = {
|
|
2
|
+
surface_flow: [
|
|
3
|
+
"frame",
|
|
4
|
+
"prototype_state",
|
|
5
|
+
"prototype_transition",
|
|
6
|
+
"annotation",
|
|
7
|
+
],
|
|
8
|
+
visual_content: ["frame", "component_variant", "token_spec", "annotation"],
|
|
9
|
+
component_control: [
|
|
10
|
+
"frame",
|
|
11
|
+
"component_variant",
|
|
12
|
+
"prototype_state",
|
|
13
|
+
"token_spec",
|
|
14
|
+
"annotation",
|
|
15
|
+
],
|
|
16
|
+
state_interaction: [
|
|
17
|
+
"component_variant",
|
|
18
|
+
"prototype_state",
|
|
19
|
+
"prototype_transition",
|
|
20
|
+
"input_spec",
|
|
21
|
+
],
|
|
22
|
+
motion: ["prototype_transition", "motion_spec", "motion_capture"],
|
|
23
|
+
adaptation_input: ["responsive_spec", "input_spec"],
|
|
24
|
+
accessibility: ["accessibility_spec", "semantic_tree", "input_spec"],
|
|
25
|
+
assets: ["asset", "token_spec", "annotation"],
|
|
26
|
+
};
|
|
27
|
+
export const DESIGN_RESOURCE_METHODS_BY_DIMENSION = {
|
|
28
|
+
surface_flow: ["layout_geometry", "interaction_trace"],
|
|
29
|
+
visual_content: ["visual_pixel", "design_token", "content"],
|
|
30
|
+
component_control: ["visual_pixel", "design_token", "component_state"],
|
|
31
|
+
state_interaction: ["component_state", "interaction_trace"],
|
|
32
|
+
motion: ["motion_timeline"],
|
|
33
|
+
adaptation_input: ["responsive_reflow", "input_method"],
|
|
34
|
+
accessibility: ["accessibility_semantics"],
|
|
35
|
+
assets: ["asset_integrity"],
|
|
36
|
+
};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { DesignResourceHandoffBlockerV1, DesignResourceHandoffCoverageV1, DesignResourceHandoffEvidenceV1 } from "./design-resource-handoff-types.js";
|
|
2
|
+
export declare function parseDesignResourceHandoffEvidence(value: unknown): DesignResourceHandoffEvidenceV1[];
|
|
3
|
+
export declare function parseDesignResourceHandoffCoverage(value: unknown): DesignResourceHandoffCoverageV1[];
|
|
4
|
+
export declare function parseDesignResourceHandoffBlockers(value: unknown): DesignResourceHandoffBlockerV1[];
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { DESIGN_RESOURCE_DIMENSIONS, DESIGN_RESOURCE_EVIDENCE_KINDS, DESIGN_RESOURCE_LOCATOR_KINDS, } from "./design-resource-handoff-types.js";
|
|
2
|
+
import { contractKey, contractKeys, sourceItemKeys, stableKey, stableKeys, verificationMethods, } from "./design-resource-handoff-shape-primitives.js";
|
|
3
|
+
import { array, literal, object, string, } from "./long-task-shape-primitives.js";
|
|
4
|
+
export function parseDesignResourceHandoffEvidence(value) {
|
|
5
|
+
return array(value, "design_resource_handoff.evidence").map((item, index) => {
|
|
6
|
+
const label = `design_resource_handoff.evidence[${index}]`;
|
|
7
|
+
const row = object(item, label, [
|
|
8
|
+
"key",
|
|
9
|
+
"resource_ref",
|
|
10
|
+
"kind",
|
|
11
|
+
"locator",
|
|
12
|
+
"condition_refs",
|
|
13
|
+
]);
|
|
14
|
+
const locator = object(row.locator, `${label}.locator`, ["kind", "value"]);
|
|
15
|
+
return {
|
|
16
|
+
key: stableKey(row.key, `${label}.key`),
|
|
17
|
+
resource_ref: stableKey(row.resource_ref, `${label}.resource_ref`),
|
|
18
|
+
kind: literal(row.kind, DESIGN_RESOURCE_EVIDENCE_KINDS, `${label}.kind`),
|
|
19
|
+
locator: {
|
|
20
|
+
kind: literal(locator.kind, DESIGN_RESOURCE_LOCATOR_KINDS, `${label}.locator.kind`),
|
|
21
|
+
value: string(locator.value, `${label}.locator.value`),
|
|
22
|
+
},
|
|
23
|
+
condition_refs: contractKeys(row.condition_refs, `${label}.condition_refs`),
|
|
24
|
+
};
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
export function parseDesignResourceHandoffCoverage(value) {
|
|
28
|
+
return array(value, "design_resource_handoff.coverage").map((item, index) => {
|
|
29
|
+
const label = `design_resource_handoff.coverage[${index}]`;
|
|
30
|
+
const row = object(item, label, [
|
|
31
|
+
"key",
|
|
32
|
+
"subject_refs",
|
|
33
|
+
"dimension",
|
|
34
|
+
"disposition",
|
|
35
|
+
"target_refs",
|
|
36
|
+
"condition_refs",
|
|
37
|
+
"evidence_refs",
|
|
38
|
+
"source_item_refs",
|
|
39
|
+
"verification_methods",
|
|
40
|
+
"rationale",
|
|
41
|
+
]);
|
|
42
|
+
return {
|
|
43
|
+
key: stableKey(row.key, `${label}.key`),
|
|
44
|
+
subject_refs: stableKeys(row.subject_refs, `${label}.subject_refs`),
|
|
45
|
+
dimension: literal(row.dimension, DESIGN_RESOURCE_DIMENSIONS, `${label}.dimension`),
|
|
46
|
+
disposition: literal(row.disposition, [
|
|
47
|
+
"covered",
|
|
48
|
+
"not_applicable",
|
|
49
|
+
"excluded_by_scope",
|
|
50
|
+
"decision_required",
|
|
51
|
+
"unavailable",
|
|
52
|
+
], `${label}.disposition`),
|
|
53
|
+
target_refs: contractKeys(row.target_refs, `${label}.target_refs`),
|
|
54
|
+
condition_refs: contractKeys(row.condition_refs, `${label}.condition_refs`),
|
|
55
|
+
evidence_refs: stableKeys(row.evidence_refs, `${label}.evidence_refs`),
|
|
56
|
+
source_item_refs: sourceItemKeys(row.source_item_refs, `${label}.source_item_refs`),
|
|
57
|
+
verification_methods: verificationMethods(row.verification_methods, `${label}.verification_methods`),
|
|
58
|
+
rationale: string(row.rationale, `${label}.rationale`),
|
|
59
|
+
};
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
export function parseDesignResourceHandoffBlockers(value) {
|
|
63
|
+
return array(value, "design_resource_handoff.acceptance_blockers").map((item, index) => {
|
|
64
|
+
const label = `design_resource_handoff.acceptance_blockers[${index}]`;
|
|
65
|
+
const row = object(item, label, [
|
|
66
|
+
"key",
|
|
67
|
+
"target_refs",
|
|
68
|
+
"subject_refs",
|
|
69
|
+
"dimensions",
|
|
70
|
+
"source_item_refs",
|
|
71
|
+
"verification_methods",
|
|
72
|
+
"description",
|
|
73
|
+
]);
|
|
74
|
+
return {
|
|
75
|
+
key: contractKey(row.key, `${label}.key`),
|
|
76
|
+
target_refs: contractKeys(row.target_refs, `${label}.target_refs`),
|
|
77
|
+
subject_refs: stableKeys(row.subject_refs, `${label}.subject_refs`),
|
|
78
|
+
dimensions: array(row.dimensions, `${label}.dimensions`).map((dimension, itemIndex) => literal(dimension, DESIGN_RESOURCE_DIMENSIONS, `${label}.dimensions[${itemIndex}]`)),
|
|
79
|
+
source_item_refs: sourceItemKeys(row.source_item_refs, `${label}.source_item_refs`),
|
|
80
|
+
verification_methods: verificationMethods(row.verification_methods, `${label}.verification_methods`),
|
|
81
|
+
description: string(row.description, `${label}.description`),
|
|
82
|
+
};
|
|
83
|
+
});
|
|
84
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { DesignResourceHandoffCoverageV1 } from "./design-resource-handoff-types.js";
|
|
2
|
+
export declare function stableKey(value: unknown, label: string): string;
|
|
3
|
+
export declare function stableKeys(value: unknown, label: string): string[];
|
|
4
|
+
export declare function contractKey(value: unknown, label: string): string;
|
|
5
|
+
export declare function contractKeys(value: unknown, label: string): string[];
|
|
6
|
+
export declare function sourceItemKeys(value: unknown, label: string): string[];
|
|
7
|
+
export declare function verificationMethods(value: unknown, label: string): DesignResourceHandoffCoverageV1["verification_methods"];
|
|
8
|
+
export declare function positiveInteger(value: unknown, label: string): number;
|
|
9
|
+
export declare function designResourceShapeFail(label: string, message: string): never;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { DESIGN_RESOURCE_VERIFICATION_METHODS } from "./design-resource-handoff-types.js";
|
|
2
|
+
import { array, literal, string } from "./long-task-shape-primitives.js";
|
|
3
|
+
const KEY = /^[a-z0-9][a-z0-9._-]*$/u;
|
|
4
|
+
const SOURCE_ITEM_KEY = /^[a-z0-9][a-z0-9-]*$/u;
|
|
5
|
+
export function stableKey(value, label) {
|
|
6
|
+
const result = string(value, label);
|
|
7
|
+
if (!KEY.test(result))
|
|
8
|
+
designResourceShapeFail(label, "must match ^[a-z0-9][a-z0-9._-]*$");
|
|
9
|
+
return result;
|
|
10
|
+
}
|
|
11
|
+
export function stableKeys(value, label) {
|
|
12
|
+
return array(value, label).map((item, index) => stableKey(item, `${label}[${index}]`));
|
|
13
|
+
}
|
|
14
|
+
export function contractKey(value, label) {
|
|
15
|
+
const result = string(value, label);
|
|
16
|
+
if (!SOURCE_ITEM_KEY.test(result))
|
|
17
|
+
designResourceShapeFail(label, "must match ^[a-z0-9][a-z0-9-]*$");
|
|
18
|
+
return result;
|
|
19
|
+
}
|
|
20
|
+
export function contractKeys(value, label) {
|
|
21
|
+
return array(value, label).map((item, index) => contractKey(item, `${label}[${index}]`));
|
|
22
|
+
}
|
|
23
|
+
export function sourceItemKeys(value, label) {
|
|
24
|
+
return array(value, label).map((item, index) => {
|
|
25
|
+
const itemLabel = `${label}[${index}]`;
|
|
26
|
+
const result = string(item, itemLabel);
|
|
27
|
+
if (!SOURCE_ITEM_KEY.test(result))
|
|
28
|
+
designResourceShapeFail(itemLabel, "must be a valid ty-source-item key");
|
|
29
|
+
return result;
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
export function verificationMethods(value, label) {
|
|
33
|
+
return array(value, label).map((item, index) => literal(item, DESIGN_RESOURCE_VERIFICATION_METHODS, `${label}[${index}]`));
|
|
34
|
+
}
|
|
35
|
+
export function positiveInteger(value, label) {
|
|
36
|
+
if (typeof value !== "number" ||
|
|
37
|
+
!Number.isSafeInteger(value) ||
|
|
38
|
+
value <= 0)
|
|
39
|
+
designResourceShapeFail(label, "must be a positive safe integer");
|
|
40
|
+
return value;
|
|
41
|
+
}
|
|
42
|
+
export function designResourceShapeFail(label, message) {
|
|
43
|
+
throw new Error(`design_resource_handoff_invalid:${label}:${message}`);
|
|
44
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { DesignResourceHandoffConditionV1, DesignResourceHandoffResourceV1, DesignResourceHandoffSubjectV1, DesignResourceHandoffTargetV1 } from "./design-resource-handoff-types.js";
|
|
2
|
+
export declare function parseDesignResourceHandoffResources(value: unknown): DesignResourceHandoffResourceV1[];
|
|
3
|
+
export declare function parseDesignResourceHandoffConditions(value: unknown): DesignResourceHandoffConditionV1[];
|
|
4
|
+
export declare function parseDesignResourceHandoffSubjects(value: unknown): DesignResourceHandoffSubjectV1[];
|
|
5
|
+
export declare function parseDesignResourceHandoffTargets(value: unknown): DesignResourceHandoffTargetV1[];
|