@starci/hfs 4.7.47 → 4.9.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/CHANGELOG.md +18 -0
- package/README.md +4 -9
- package/emit/operation-schemas.mjs +3 -0
- package/lint/bound-sys.cjs +2 -2
- package/package.json +11 -5
- package/runtime/engine/admission.mjs +1 -1
- package/runtime/engine/config.mjs +2 -2
- package/runtime/engine/db/machine.mjs +2 -3
- package/runtime/engine/invalid-config.mjs +1 -1
- package/runtime/knowledge/grammars/{starci → core}/playbook.yaml +3 -3
- package/runtime/knowledge/grammars/index.yaml +14 -23
- package/runtime/knowledge/grammars/offset-pop/playbook.yaml +2 -2
- package/runtime/knowledge/hfs/canon-pins.yaml +23 -23
- package/runtime/knowledge/hfs/fe-stack.yaml +1 -1
- package/runtime/knowledge/hfs/rules.yaml +83 -60
- package/runtime/modules/kernel/failure-codes.yaml +23 -13
- package/runtime/scripts/guards/git-policy.mjs +2 -3
- package/runtime/scripts/hfs/rule-catalog.mjs +2 -2
- package/runtime/scripts/hfs/rules/pipeline.mjs +5 -5
- package/runtime/scripts/lib/example-refs.mjs +10 -3
- package/runtime/scripts/lib/grammar-lint.mjs +16 -3
- package/runtime/scripts/lib/package-at.mjs +5 -6
- package/runtime/scripts/lib/roots.mjs +2 -2
- package/runtime/scripts/work/draw/draw-dna-findings.mjs +23 -12
- package/runtime/scripts/work/draw/draw-dna.mjs +14 -8
- package/scaffold/app.mjs +6 -6
- package/src/main.mjs +3 -3
- package/sync/hygiene.mjs +1 -1
- package/sync/index.mjs +15 -2
- package/sync/managed.mjs +3 -2
- package/templates/app/codegen-graphql.mjs.partial +4 -4
- package/templates/app/quality-config/sonar-project.properties +4 -2
- package/runtime/engine/canonical-json.mjs +0 -14
- package/runtime/engine/db/ledger-open.mjs +0 -38
- package/runtime/engine/db/ledger-paths.mjs +0 -87
- package/runtime/engine/db/ledger.mjs +0 -1068
- package/runtime/engine/db/provision-authority.mjs +0 -76
- package/runtime/engine/db/provision-operations.mjs +0 -146
- package/runtime/packages/grammar/scripts/build-stamp.mjs +0 -98
- package/runtime/scripts/agent/codex-app-server.mjs +0 -79
- package/runtime/scripts/agent/codex-mirror-source.mjs +0 -34
- package/runtime/scripts/agent/host-agents.mjs +0 -80
- package/runtime/scripts/agent/launch-trust-policy.mjs +0 -53
- package/runtime/scripts/agent/trust-launch.mjs +0 -191
- package/runtime/scripts/agent/trust.mjs +0 -523
- package/runtime/scripts/api/fs/temp-path.mjs +0 -8
- package/runtime/scripts/api/git/diff-names.mjs +0 -8
- package/runtime/scripts/api/git/head-time.mjs +0 -10
- package/runtime/scripts/api/git/index-lock.mjs +0 -78
- package/runtime/scripts/api/git/log.mjs +0 -5
- package/runtime/scripts/api/git/merge-tree.mjs +0 -5
- package/runtime/scripts/api/git/rev-list.mjs +0 -5
- package/runtime/scripts/api/git/rev-parse.mjs +0 -13
- package/runtime/scripts/api/git/status-query.mjs +0 -5
- package/runtime/scripts/api/git/worktree-list-query.mjs +0 -5
- package/runtime/scripts/api/node/spawn-node.mjs +0 -6
- package/runtime/scripts/api/npm/dist-tag.mjs +0 -8
- package/runtime/scripts/api/npm/lib.mjs +0 -32
- package/runtime/scripts/api/npm/pack-dry-run.mjs +0 -9
- package/runtime/scripts/api/npm/run-npm.mjs +0 -6
- package/runtime/scripts/api/npm/view.mjs +0 -30
- package/runtime/scripts/api/npm/whoami.mjs +0 -8
- package/runtime/scripts/api/process/hide-child-windows.mjs +0 -31
- package/runtime/scripts/api/process/kill-tree.mjs +0 -22
- package/runtime/scripts/api/process/lib.mjs +0 -125
- package/runtime/scripts/api/process/probe-guard-command.mjs +0 -74
- package/runtime/scripts/api/process/process-list.mjs +0 -28
- package/runtime/scripts/api/process/system-tool.mjs +0 -37
- package/runtime/scripts/gates/canon-digest.mjs +0 -81
- package/runtime/scripts/gates/canon-pins.mjs +0 -119
- package/runtime/scripts/gates/gate-input.mjs +0 -98
- package/runtime/scripts/gates/gate.mjs +0 -636
- package/runtime/scripts/gates/grammar-dist.mjs +0 -206
- package/runtime/scripts/gates/output.mjs +0 -40
- package/runtime/scripts/gates/runtime-package-selection.mjs +0 -27
- package/runtime/scripts/gates/test-world-run.mjs +0 -252
- package/runtime/scripts/gates/type-impact.mjs +0 -277
- package/runtime/scripts/gates/type-program.mjs +0 -113
- package/runtime/scripts/guards/assigned-command.mjs +0 -12
- package/runtime/scripts/guards/call-rights.mjs +0 -47
- package/runtime/scripts/guards/command-guard.mjs +0 -468
- package/runtime/scripts/guards/command-policy.mjs +0 -342
- package/runtime/scripts/guards/critic-reach.mjs +0 -82
- package/runtime/scripts/guards/deps-guard.mjs +0 -150
- package/runtime/scripts/guards/env-dump-verdict.mjs +0 -89
- package/runtime/scripts/guards/guards-root.mjs +0 -27
- package/runtime/scripts/guards/hook-install.mjs +0 -343
- package/runtime/scripts/guards/hook-verdict.mjs +0 -12
- package/runtime/scripts/guards/install-verdict.mjs +0 -41
- package/runtime/scripts/guards/kernel-seat.mjs +0 -63
- package/runtime/scripts/guards/launch-verdict.mjs +0 -74
- package/runtime/scripts/guards/op-starci.mjs +0 -16
- package/runtime/scripts/guards/op-write-scope.mjs +0 -27
- package/runtime/scripts/guards/orca-self-lifecycle.mjs +0 -39
- package/runtime/scripts/guards/process-kill-verdict.mjs +0 -106
- package/runtime/scripts/guards/protected-zone.mjs +0 -56
- package/runtime/scripts/guards/refusals.mjs +0 -27
- package/runtime/scripts/guards/rights.mjs +0 -268
- package/runtime/scripts/guards/seat-tools.mjs +0 -47
- package/runtime/scripts/guards/shell-commands.mjs +0 -328
- package/runtime/scripts/guards/supervisor-seat.mjs +0 -80
- package/runtime/scripts/guards/verdict-process.mjs +0 -81
- package/runtime/scripts/lib/app-data.mjs +0 -11
- package/runtime/scripts/lib/canon-binding-text.mjs +0 -129
- package/runtime/scripts/lib/check-format.mjs +0 -7
- package/runtime/scripts/lib/failure-code-reads.mjs +0 -100
- package/runtime/scripts/lib/failure-code-template-errors.mjs +0 -161
- package/runtime/scripts/lib/git-dir.mjs +0 -120
- package/runtime/scripts/lib/guard-command.mjs +0 -24
- package/runtime/scripts/lib/interrupt-cleanup.mjs +0 -38
- package/runtime/scripts/lib/json-formats.mjs +0 -34
- package/runtime/scripts/lib/managed-json.mjs +0 -51
- package/runtime/scripts/lib/menu-parts.mjs +0 -11
- package/runtime/scripts/lib/op-gate.mjs +0 -15
- package/runtime/scripts/lib/provision-control-failure.mjs +0 -32
- package/runtime/scripts/lib/release-version.mjs +0 -134
- package/runtime/scripts/lib/runtime-location.mjs +0 -21
- package/runtime/scripts/lib/runtime-package-inventory.mjs +0 -21
- package/runtime/scripts/lib/runtime-package-manifests.mjs +0 -84
- package/runtime/scripts/lib/runtime-publication-tags.mjs +0 -56
- package/runtime/scripts/lib/seat-env.mjs +0 -43
- package/runtime/scripts/lib/sonar-new-code.mjs +0 -58
- package/runtime/scripts/lib/source-package-imports.mjs +0 -111
- package/runtime/scripts/lib/tar-files.mjs +0 -39
- package/runtime/scripts/lib/uat-run-folder.mjs +0 -118
- package/runtime/scripts/lib/worktree-record.mjs +0 -14
- package/runtime/scripts/machine/ask-receipts.mjs +0 -113
- package/runtime/scripts/machine/evidence-store.mjs +0 -223
- package/runtime/scripts/machine/home.mjs +0 -216
- package/runtime/scripts/machine/host-lock.mjs +0 -184
- package/runtime/scripts/machine/lock-recovery.mjs +0 -55
- package/runtime/scripts/machine/roles-contract.mjs +0 -90
- package/runtime/scripts/machine/runtime-change.mjs +0 -20
- package/runtime/scripts/machine/workflow-tree.mjs +0 -122
- package/runtime/scripts/reconciler/checklist-items.mjs +0 -7
- package/runtime/scripts/reconciler/guard-row.mjs +0 -25
- package/runtime/scripts/work/brand/brand-colour.mjs +0 -125
- package/runtime/scripts/work/brand/brand-tokens.mjs +0 -167
- package/runtime/scripts/work/brand/brand-value-source.mjs +0 -106
- package/runtime/scripts/work/brand/brand.mjs +0 -892
- package/runtime/scripts/work/draw/draw-layer.mjs +0 -306
- package/runtime/scripts/work/draw/draw-rationale-page.mjs +0 -237
- package/runtime/scripts/work/draw/draw-rationale-ref.mjs +0 -14
- package/runtime/scripts/work/draw/draw-rationale.mjs +0 -353
- package/runtime/scripts/work/draw/draw-source.mjs +0 -412
- package/runtime/scripts/work/draw/draw-taste.mjs +0 -278
- package/runtime/scripts/work/draw/draw-typecheck.mjs +0 -72
- package/runtime/scripts/work/draw/rationale-findings.mjs +0 -205
- package/runtime/scripts/work/draw-grammar.mjs +0 -175
- package/runtime/scripts/work/draw-render-page.mjs +0 -188
- package/runtime/scripts/work/draw-render.mjs +0 -626
- package/runtime/scripts/work/png.mjs +0 -237
- package/runtime/scripts/work/render-tools.mjs +0 -64
|
@@ -1,175 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// draw-grammar.mjs — which @starci/grammar a drawing renders against (owner ruling 2026-09-27: the draw must render
|
|
3
|
-
// against the grammar version the product will SHIP).
|
|
4
|
-
//
|
|
5
|
-
// Candidates, in order:
|
|
6
|
-
// product the grammar the product has installed (resolved from the product app dir, as its bundler would)
|
|
7
|
-
// claude-dist the runtime-built package (.claude/packages/grammar/dist, the grammar main carries - possibly
|
|
8
|
-
// newer than the product's install, e.g. Meter segments 0.5.2 against an installed 0.5.0)
|
|
9
|
-
// The product's install wins when it SATISFIES what the draw needs, and "satisfies" is decided by TypeScript: the
|
|
10
|
-
// draw file type-checks against that candidate's types (scripts/work/draw/draw-source.mjs typecheckDraw). When only
|
|
11
|
-
// claude-dist satisfies, the drawing renders against it through an alias in the draw bundle ONLY (draw-render.mjs
|
|
12
|
-
// --grammar-root), the provenance says so (grammarSource claude-dist@<v>) and an upgrade of the product is OWED
|
|
13
|
-
// (upgradeOwed {status: owed, from, to, range, inRange}) - interface.implement bumps the product's dependency; the
|
|
14
|
-
// draw never touches the product's package.json. When neither satisfies: DRAW_TYPECHECK_FAILED.
|
|
15
|
-
//
|
|
16
|
-
// starci work draw-grammar --product <app dir> [--file <X.draw.tsx>] [--grammar auto|product|claude-dist]
|
|
17
|
-
// [--grammar-dist <package root>] (default .claude/packages/grammar; env STARCI_GRAMMAR_DIST; a lane checkout
|
|
18
|
-
// without a built dist points it at the live checkout's)
|
|
19
|
-
import path from 'node:path';
|
|
20
|
-
import { fileURLToPath } from 'node:url';
|
|
21
|
-
import { revParseQuery } from '../api/git/rev-parse-query.mjs';
|
|
22
|
-
import { findPackage } from '../lib/package-at.mjs';
|
|
23
|
-
import { readJsonFile } from '../lib/json.mjs';
|
|
24
|
-
import { GRAMMAR_PACKAGE, typecheckDraw } from './draw/draw-typecheck.mjs';
|
|
25
|
-
import { grammarDistStatus } from '../gates/grammar-dist.mjs';
|
|
26
|
-
import { isFile } from './work-io.mjs';
|
|
27
|
-
import { isMain } from '../lib/is-main.mjs';
|
|
28
|
-
|
|
29
|
-
const GRAMMAR_SOURCES = Object.freeze(['product', 'claude-dist']);
|
|
30
|
-
export const PREFERENCES = Object.freeze(['auto', ...GRAMMAR_SOURCES]);
|
|
31
|
-
const SKILL_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..');
|
|
32
|
-
|
|
33
|
-
/** The product's declared range for the grammar (dependencies/devDependencies/peerDependencies), or null. */
|
|
34
|
-
function productRangeOf(productDir) {
|
|
35
|
-
const pkg = readJsonFile(path.join(productDir, 'package.json'));
|
|
36
|
-
return pkg?.dependencies?.[GRAMMAR_PACKAGE] ?? pkg?.devDependencies?.[GRAMMAR_PACKAGE] ?? pkg?.peerDependencies?.[GRAMMAR_PACKAGE] ?? null;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/** A built grammar package root: its package.json plus dist/<family>/index.d.ts and .js. */
|
|
40
|
-
const builtGrammar = (root) => isFile(path.join(root, 'package.json')) && isFile(path.join(root, 'dist', 'core', 'index.d.ts')) && isFile(path.join(root, 'dist', 'core', 'index.js'));
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* The main worktree of the git checkout `dir` lives in (a lane worktree's live runtime), or null: a lane checkout never
|
|
44
|
-
* builds packages/grammar/dist (dist/ is untracked), so its drawings resolve the live runtime's build.
|
|
45
|
-
*/
|
|
46
|
-
function mainWorktreeOf(dir) {
|
|
47
|
-
try {
|
|
48
|
-
const r = revParseQuery(['--path-format=absolute', '--git-common-dir'], { dir, timeout: 10_000 });
|
|
49
|
-
const common = r.status === 0 ? r.stdout.trim() : '';
|
|
50
|
-
if (!common || path.basename(common) !== '.git') return null;
|
|
51
|
-
const main = path.dirname(common);
|
|
52
|
-
return path.resolve(main) === path.resolve(dir) ? null : main;
|
|
53
|
-
} catch { return null; }
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* The claude-dist roots, best first: --grammar-dist / STARCI_GRAMMAR_DIST when given (used as is), else the runtime's
|
|
58
|
-
* packages/grammar when its build is FRESH (grammar-dist.mjs: stamped from the current source), else the live main
|
|
59
|
-
* worktree's fresh build (a lane checkout), else the runtime's built but stale dist - named stale, never silently.
|
|
60
|
-
*/
|
|
61
|
-
function claudeDistRoots({ skillRoot = SKILL_ROOT, grammarDist = null, env = process.env, status = grammarDistStatus, mainOf = mainWorktreeOf } = {}) {
|
|
62
|
-
const pinned = grammarDist ?? env.STARCI_GRAMMAR_DIST ?? null;
|
|
63
|
-
if (pinned) return [{ root: path.resolve(pinned), status: null, via: grammarDist ? '--grammar-dist' : 'STARCI_GRAMMAR_DIST' }];
|
|
64
|
-
const own = path.join(skillRoot, 'packages', 'grammar');
|
|
65
|
-
const main = mainOf(skillRoot);
|
|
66
|
-
const tried = [{ root: own, via: 'runtime' }, ...(main ? [{ root: path.join(main, 'packages', 'grammar'), via: 'main-worktree' }] : [])]
|
|
67
|
-
.filter((c) => builtGrammar(c.root)).map((c) => ({ ...c, status: status(c.root) }));
|
|
68
|
-
const fresh = tried.filter((c) => c.status?.ok);
|
|
69
|
-
return fresh.length ? [fresh[0]] : tried.slice(0, 1);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
function grammarCandidates({ productDir, skillRoot = SKILL_ROOT, grammarDist = null, claudeRoots = null }) {
|
|
73
|
-
const out = [];
|
|
74
|
-
const installed = productDir ? findPackage([productDir], [GRAMMAR_PACKAGE]) : null;
|
|
75
|
-
if (installed && builtGrammar(installed.root)) out.push({ source: 'product', root: installed.root, version: installed.version });
|
|
76
|
-
for (const c of claudeRoots ?? claudeDistRoots({ skillRoot, grammarDist })) {
|
|
77
|
-
if (!builtGrammar(c.root)) continue;
|
|
78
|
-
out.push({ source: 'claude-dist', root: c.root, version: readJsonFile(path.join(c.root, 'package.json'))?.version ?? null, via: c.via,
|
|
79
|
-
...(c.status ? { dist: { state: c.status.state, ok: c.status.ok, detail: c.status.detail } } : {}) });
|
|
80
|
-
}
|
|
81
|
-
return out;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
const parse = (v) => { const m = /^(\d+)\.(\d+)\.(\d+)/.exec(String(v ?? '').trim()); return m ? m.slice(1, 4).map(Number) : null; };
|
|
85
|
-
const cmp = (a, b) => (a[0] - b[0]) || (a[1] - b[1]) || (a[2] - b[2]);
|
|
86
|
-
/** Minimal semver range test: exact, ^, ~, >=, * (enough for a dependency line); null when unreadable. */
|
|
87
|
-
function satisfiesRange(version, range) {
|
|
88
|
-
const v = parse(version);
|
|
89
|
-
if (!v || range == null) return null;
|
|
90
|
-
const r = String(range).trim();
|
|
91
|
-
if (r === '*' || r === 'latest' || r === '') return true;
|
|
92
|
-
const m = /^(\^|~|>=|=)?\s*v?(\d+\.\d+\.\d+)/.exec(r);
|
|
93
|
-
if (!m) return null;
|
|
94
|
-
const base = parse(m[2]);
|
|
95
|
-
if (cmp(v, base) < 0) return false;
|
|
96
|
-
if (m[1] === '>=') return true;
|
|
97
|
-
if (!m[1] || m[1] === '=') return cmp(v, base) === 0;
|
|
98
|
-
if (m[1] === '~') return v[0] === base[0] && v[1] === base[1];
|
|
99
|
-
// caret: the left-most non-zero part is fixed
|
|
100
|
-
if (base[0] > 0) return v[0] === base[0];
|
|
101
|
-
if (base[1] > 0) return v[0] === 0 && v[1] === base[1];
|
|
102
|
-
return v[0] === 0 && v[1] === 0 && v[2] === base[2];
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
function tryGrammarCandidates(candidates, { file, productDir, typecheck }) {
|
|
106
|
-
const attempts = [];
|
|
107
|
-
let pick = null;
|
|
108
|
-
for (const c of candidates) {
|
|
109
|
-
if (!file) { pick = c; break; }
|
|
110
|
-
const r = typecheck({ file, productDir, grammarRoot: c.root });
|
|
111
|
-
attempts.push({ source: c.source, version: c.version, root: c.root, ok: r.ok, errors: r.errors.slice(0, 20), typescript: r.typescript ?? null, ...(c.dist ? { dist: c.dist } : {}) });
|
|
112
|
-
if (r.ok) { pick = c; break; }
|
|
113
|
-
}
|
|
114
|
-
return { attempts, pick };
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
function upgradeOwedFor(pick, product, productRange) {
|
|
118
|
-
return pick?.source === 'claude-dist' ? { status: 'owed', package: GRAMMAR_PACKAGE, from: product?.version ?? null, to: pick.version, range: productRange,
|
|
119
|
-
inRange: satisfiesRange(pick.version, productRange), why: product ? `the product's installed ${GRAMMAR_PACKAGE}@${product.version} does not satisfy the drawing (it fails to type-check); ${pick.version} does` : `the product has no built ${GRAMMAR_PACKAGE} install` } : null;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
function missingGrammarError(candidates, skillRoot) {
|
|
123
|
-
return candidates.length
|
|
124
|
-
? `no grammar candidate type-checks the drawing (${candidates.map((candidate) => candidate.source + '@' + candidate.version + (candidate.dist && !candidate.dist.ok ? ' - its dist is ' + candidate.dist.state + ': ' + candidate.dist.detail + '; run npm run build in packages/grammar' : '')).join(', ')})`
|
|
125
|
-
: `no built ${GRAMMAR_PACKAGE} (product install, ${path.join(skillRoot, 'packages', 'grammar', 'dist')} or the main worktree's; run npm run build in packages/grammar, or pass --grammar-dist)`;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* Resolve the grammar a draw file renders against. `typecheck` is injectable (tests). Without `file` the first
|
|
130
|
-
* candidate of the preference wins untested. Returns {ok, pick, grammarSource, productVersion, productRange,
|
|
131
|
-
* upgradeOwed, attempts:[{source, version, root, ok, errors}], error?}.
|
|
132
|
-
*/
|
|
133
|
-
export function resolveDrawGrammar({ file = null, productDir, skillRoot = SKILL_ROOT, grammarDist = null, prefer = 'auto', typecheck = typecheckDraw, claudeRoots = null }) {
|
|
134
|
-
if (!PREFERENCES.includes(prefer)) throw new Error(`--grammar must be one of ${PREFERENCES.join('|')}`);
|
|
135
|
-
const all = grammarCandidates({ productDir, skillRoot, grammarDist, claudeRoots });
|
|
136
|
-
const product = all.find((c) => c.source === 'product') ?? null;
|
|
137
|
-
const productRange = productRangeOf(productDir);
|
|
138
|
-
const candidates = prefer === 'auto' ? all : all.filter((c) => c.source === prefer);
|
|
139
|
-
const { attempts, pick } = tryGrammarCandidates(candidates, { file, productDir, typecheck });
|
|
140
|
-
const upgradeOwed = upgradeOwedFor(pick, product, productRange);
|
|
141
|
-
return {
|
|
142
|
-
ok: Boolean(pick), pick, grammarSource: pick ? `${pick.source}@${pick.version}` : null, productVersion: product?.version ?? null, productRange, upgradeOwed, attempts,
|
|
143
|
-
...(pick ? {} : { error: missingGrammarError(candidates, skillRoot) }),
|
|
144
|
-
};
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
/** The file a grammar subpath import resolves to inside a grammar root (package exports; `import` for JS). */
|
|
148
|
-
export function grammarEntry(root, subpath = '') {
|
|
149
|
-
const pkg = readJsonFile(path.join(root, 'package.json'));
|
|
150
|
-
const key = subpath ? `./${subpath.replace(/^\//, '')}` : '.';
|
|
151
|
-
const mapped = pkg?.exports?.[key];
|
|
152
|
-
const target = typeof mapped === 'string' ? mapped : mapped?.import ?? mapped?.default ?? mapped?.style ?? null;
|
|
153
|
-
if (target) return path.join(root, target);
|
|
154
|
-
if (!subpath) return path.join(root, 'dist', 'core', 'index.js');
|
|
155
|
-
return path.join(root, subpath);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
async function main(argv) {
|
|
159
|
-
const val = (k) => { const i = argv.indexOf(k); return i >= 0 ? argv[i + 1] : null; };
|
|
160
|
-
const productDir = val('--product');
|
|
161
|
-
if (!productDir) { process.stderr.write('use: starci work draw-grammar --product <app dir> [--file <X.draw.tsx>] [--grammar auto|product|claude-dist] [--json]\n'); return 2; }
|
|
162
|
-
const r = resolveDrawGrammar({ file: val('--file') ? path.resolve(val('--file')) : null, productDir: path.resolve(productDir), prefer: val('--grammar') ?? 'auto', grammarDist: val('--grammar-dist') });
|
|
163
|
-
if (argv.includes('--json')) process.stdout.write(`${JSON.stringify(r, null, 2)}\n`);
|
|
164
|
-
else {
|
|
165
|
-
const summary = r.ok ? 'grammar ' + r.grammarSource : 'UNRESOLVED: ' + r.error;
|
|
166
|
-
const upgrade = r.upgradeOwed ? `; product upgrade owed ${r.upgradeOwed.from} -> ${r.upgradeOwed.to} (range ${r.upgradeOwed.range}, in range ${r.upgradeOwed.inRange})` : '';
|
|
167
|
-
const attempts = r.attempts.map((a) => ' ' + a.source + '@' + a.version + ': ' + (a.ok ? 'type-checks' : a.errors.length + ' error(s): ' + a.errors.slice(0, 3).map((e) => e.code + ' ' + e.message).join(' | '))).join('\n');
|
|
168
|
-
process.stdout.write(`${summary}${upgrade}\n${attempts}\n`);
|
|
169
|
-
}
|
|
170
|
-
return r.ok ? 0 : 1;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
if (isMain(import.meta.url)) {
|
|
174
|
-
try { process.exitCode = await main(process.argv.slice(2)); } catch (e) { process.stderr.write(`draw-grammar: ${e?.stack ?? e}\n`); process.exitCode = 2; }
|
|
175
|
-
}
|
|
@@ -1,188 +0,0 @@
|
|
|
1
|
-
// draw-render-page.mjs - the in-page half of draw-render.mjs. A page function is serialized to a string, so it cannot
|
|
2
|
-
// reach the module scope: measureInPage assembles one script from the source of every function below (each is a
|
|
3
|
-
// top-level declaration that reads only the page's globals and its own parameters) and runs it in the page.
|
|
4
|
-
|
|
5
|
-
function measurePage({ generic, exemptSelector, layoutAttr = 'data-draw-layout' }) {
|
|
6
|
-
const faces = [...document.fonts].map((face) => ({ family: face.family.replace(/^(["'])(.*)\1$/, '$2'), weight: face.weight, style: face.style, status: face.status }));
|
|
7
|
-
const fontData = measureFonts(generic);
|
|
8
|
-
const de = document.documentElement;
|
|
9
|
-
const pageWidth = de.clientWidth;
|
|
10
|
-
const scrollWidth = Math.max(de.scrollWidth, document.body?.scrollWidth ?? 0);
|
|
11
|
-
const overflowing = measureOverflow(pageWidth, scrollWidth);
|
|
12
|
-
const root = document.getElementById('root');
|
|
13
|
-
const accentExempt = measureAccentExempt(exemptSelector);
|
|
14
|
-
const artwork = measureArtwork();
|
|
15
|
-
const anatomy = measureAnatomy();
|
|
16
|
-
const { ownership, dom } = measureOwnership(root, layoutAttr);
|
|
17
|
-
return { ownership, dom, anatomy, accentExempt, artwork, faces, stacks: [...fontData.stacks], local: fontData.local, pageWidth, innerWidth, scrollWidth, overflowing, documentHeight: de.scrollHeight,
|
|
18
|
-
rendered: document.documentElement.dataset.drawHarness === 'component' ? Boolean(root && root.childElementCount) : null };
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function visible(el) {
|
|
22
|
-
const style = getComputedStyle(el);
|
|
23
|
-
return style.display !== 'none' && style.visibility !== 'hidden';
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function measureFonts(genericFamilies) {
|
|
27
|
-
const stacks = new Set();
|
|
28
|
-
const walker = document.createTreeWalker(document.body ?? document.documentElement, NodeFilter.SHOW_TEXT);
|
|
29
|
-
for (let node = walker.nextNode(); node; node = walker.nextNode()) {
|
|
30
|
-
const el = node.parentElement;
|
|
31
|
-
if (el && node.textContent.trim() && !['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEMPLATE'].includes(el.tagName) && visible(el)) { stacks.add(getComputedStyle(el).fontFamily); }
|
|
32
|
-
}
|
|
33
|
-
for (const el of document.querySelectorAll('input,textarea,select,button')) {
|
|
34
|
-
if (visible(el)) { stacks.add(getComputedStyle(el).fontFamily); }
|
|
35
|
-
}
|
|
36
|
-
const ctx = document.createElement('canvas').getContext('2d');
|
|
37
|
-
const sample = 'mmmmmmmmmmlli10WQ@#';
|
|
38
|
-
const width = (font) => { ctx.font = `72px ${font}`; return ctx.measureText(sample).width; };
|
|
39
|
-
const families = new Set([...stacks].flatMap((stack) => stack.split(',').map((family) => family.trim().replace(/^(["'])(.*)\1$/, '$2'))).filter((family) => family && !genericFamilies.includes(family.toLowerCase())));
|
|
40
|
-
const local = [...families].filter((family) => ['monospace', 'serif', 'sans-serif'].some((base) => width(`"${family}", ${base}`) !== width(base)));
|
|
41
|
-
return { stacks, local };
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function measureOverflow(width, scroll) {
|
|
45
|
-
if (scroll > width) {
|
|
46
|
-
return [...document.querySelectorAll('body *')].filter((el) => el.getBoundingClientRect().right > width + 0.5)
|
|
47
|
-
.slice(0, 10).map((el) => el.tagName.toLowerCase() + (el.id ? '#' + el.id : '') + (typeof el.className === 'string' && el.className ? '.' + el.className.trim().split(/\s+/).slice(0, 3).join('.') : ''));
|
|
48
|
-
}
|
|
49
|
-
return [];
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function measureAccentExempt(exemptSelector) {
|
|
53
|
-
try {
|
|
54
|
-
return [...document.querySelectorAll(exemptSelector)].map((el) => el.getBoundingClientRect())
|
|
55
|
-
.filter((rect) => rect.width > 0 && rect.height > 0).map((rect) => ({ x: rect.left + scrollX, y: rect.top + scrollY, width: rect.width, height: rect.height }));
|
|
56
|
-
} catch { return []; }
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function measureArtwork() {
|
|
60
|
-
try {
|
|
61
|
-
return [...document.querySelectorAll('img')].filter((el) => visible(el) && (el.currentSrc || el.src)).map((el) => {
|
|
62
|
-
const rect = el.getBoundingClientRect();
|
|
63
|
-
let x0 = rect.left, y0 = rect.top, x1 = rect.right, y1 = rect.bottom;
|
|
64
|
-
for (let ancestor = el.parentElement; ancestor; ancestor = ancestor.parentElement) {
|
|
65
|
-
const style = getComputedStyle(ancestor);
|
|
66
|
-
if (style.overflowX !== 'visible' || style.overflowY !== 'visible') {
|
|
67
|
-
const clip = ancestor.getBoundingClientRect();
|
|
68
|
-
x0 = Math.max(x0, clip.left); y0 = Math.max(y0, clip.top); x1 = Math.min(x1, clip.right); y1 = Math.min(y1, clip.bottom);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
return { src: el.currentSrc || el.src, slot: el.closest('[data-artwork-slot]')?.dataset.artworkSlot ?? null,
|
|
72
|
-
x: x0 + scrollX, y: y0 + scrollY, width: Math.max(0, x1 - x0), height: Math.max(0, y1 - y0) };
|
|
73
|
-
}).filter((art) => art.width > 0 && art.height > 0);
|
|
74
|
-
} catch { return []; }
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function tagOf(el) {
|
|
78
|
-
return el.tagName.toLowerCase() + (el.id ? '#' + el.id : '');
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
function partIn(rootEl, names) {
|
|
82
|
-
return [...rootEl.querySelectorAll('[data-grammar-part]')].find((part) => names.includes(part.dataset.grammarPart.trim()));
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
function measureAnatomy() {
|
|
86
|
-
const anatomy = { alerts: [], meters: [] };
|
|
87
|
-
try {
|
|
88
|
-
const probe = document.createElement('div');
|
|
89
|
-
probe.style.cssText = 'position:absolute;visibility:hidden;background-color:var(--surface)';
|
|
90
|
-
document.body.appendChild(probe);
|
|
91
|
-
const surface = getComputedStyle(probe).backgroundColor;
|
|
92
|
-
probe.remove();
|
|
93
|
-
for (const el of document.querySelectorAll('[data-grammar-component="Alert"],[data-component="Alert"]')) {
|
|
94
|
-
const alert = measureAlert(el, surface);
|
|
95
|
-
if (alert) { anatomy.alerts.push(alert); }
|
|
96
|
-
}
|
|
97
|
-
for (const el of document.querySelectorAll('[data-grammar-component="Meter"],[data-component="Meter"]')) {
|
|
98
|
-
const meter = measureMeter(el);
|
|
99
|
-
if (meter) { anatomy.meters.push(meter); }
|
|
100
|
-
}
|
|
101
|
-
} catch { /* the anatomy is advisory measurement; the static gate still runs */ }
|
|
102
|
-
return anatomy;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
function measureAlert(el, surface) {
|
|
106
|
-
if ('grammarPart' in el.dataset) { return null; }
|
|
107
|
-
const indicator = partIn(el, ['alert-indicator', 'indicator']) ?? el.querySelector('.alert__indicator');
|
|
108
|
-
const title = partIn(el, ['alert-title', 'title']) ?? el.querySelector('.alert__title');
|
|
109
|
-
const box = indicator?.getBoundingClientRect();
|
|
110
|
-
return { desc: tagOf(el), background: getComputedStyle(el).backgroundColor, surface,
|
|
111
|
-
indicator: box ? { width: box.width, height: box.height } : null, tile: Boolean(el.querySelector('[data-grammar-component="IconTile"],[data-component="IconTile"]')),
|
|
112
|
-
indicatorColor: indicator ? getComputedStyle(indicator.querySelector('svg') ?? indicator).color : null, titleColor: title ? getComputedStyle(title).color : null };
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
function measureMeter(el) {
|
|
116
|
-
if ('grammarPart' in el.dataset) { return null; }
|
|
117
|
-
const track = partIn(el, ['meter-track', 'track']) ?? el.querySelector('.meter__track');
|
|
118
|
-
const band = el.parentElement;
|
|
119
|
-
if (!track || !band) { return null; }
|
|
120
|
-
const style = getComputedStyle(band);
|
|
121
|
-
const trackRect = track.getBoundingClientRect();
|
|
122
|
-
const segments = [...el.querySelectorAll('[data-grammar-part*="segment"]:not([data-grammar-part$="segments"]), .starci-core-meter-segment, [data-grammar-meter-segment]')]
|
|
123
|
-
.map((part) => part.getBoundingClientRect()).map((rect) => ({ x: rect.left, width: rect.width }));
|
|
124
|
-
const segmented = 'grammarMeterSegments' in el.dataset || 'segments' in el.dataset || segments.length > 1;
|
|
125
|
-
return { desc: tagOf(el), segmented, track: { width: trackRect.width, height: trackRect.height },
|
|
126
|
-
band: { width: band.clientWidth - Number.parseFloat(style.paddingLeft || '0') - Number.parseFloat(style.paddingRight || '0') }, segments };
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
function marked(el) {
|
|
130
|
-
return 'component' in el.dataset || [...el.attributes].some((attr) => attr.name.startsWith('data-grammar-')) || [...el.classList].some((name) => name.startsWith('starci-core-'));
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
function paints(el) {
|
|
134
|
-
const style = getComputedStyle(el);
|
|
135
|
-
if (style.display === 'none' || style.visibility === 'hidden') { return false; }
|
|
136
|
-
if (['img', 'svg', 'canvas', 'video', 'picture'].includes(el.tagName.toLowerCase())) { return true; }
|
|
137
|
-
if ([...el.childNodes].some((node) => node.nodeType === 3 && node.textContent.trim())) { return true; }
|
|
138
|
-
if (style.backgroundColor && !/^(?:transparent|rgba\(0, 0, 0, 0\))$/.test(style.backgroundColor)) { return true; }
|
|
139
|
-
if (style.backgroundImage && style.backgroundImage !== 'none') { return true; }
|
|
140
|
-
if (['Top', 'Right', 'Bottom', 'Left'].some((side) => Number.parseFloat(style[`border${side}Width`]) > 0 && style[`border${side}Style`] !== 'none' && !/rgba\(0, 0, 0, 0\)/.test(style[`border${side}Color`]))) { return true; }
|
|
141
|
-
return Boolean(style.boxShadow && style.boxShadow !== 'none');
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
function measureOwnership(rootEl, layoutAttribute) {
|
|
145
|
-
let ownership = null, dom = null;
|
|
146
|
-
if (document.documentElement.dataset.drawHarness !== 'component' || !rootEl) { return { ownership, dom }; }
|
|
147
|
-
const components = new Set();
|
|
148
|
-
const unowned = [];
|
|
149
|
-
let layoutElements = 0;
|
|
150
|
-
for (const el of rootEl.querySelectorAll('*')) {
|
|
151
|
-
if ('component' in el.dataset) { components.add(el.dataset.component); }
|
|
152
|
-
layoutElements += Number(el.hasAttribute(layoutAttribute));
|
|
153
|
-
if ((el.closest('svg') && el.tagName.toLowerCase() !== 'svg') || !paints(el)) { continue; }
|
|
154
|
-
const owner = ownerOf(el, rootEl, layoutAttribute);
|
|
155
|
-
if (owner !== 'grammar') { unowned.push(ownershipLabel(el, owner)); }
|
|
156
|
-
}
|
|
157
|
-
ownership = { components: [...components].sort((left, right) => { if (left < right) { return -1; } if (left > right) { return 1; } return 0; }), layoutElements, unownedCount: unowned.length, unowned: unowned.slice(0, 20) };
|
|
158
|
-
const clone = document.documentElement.cloneNode(true);
|
|
159
|
-
for (const el of clone.querySelectorAll('[data-component]')) {
|
|
160
|
-
if (!('grammarComponent' in el.dataset)) { el.dataset.grammarComponent = el.dataset.component; }
|
|
161
|
-
}
|
|
162
|
-
for (const el of clone.querySelectorAll('script')) { el.remove(); }
|
|
163
|
-
dom = `<!doctype html>
|
|
164
|
-
${clone.outerHTML}`;
|
|
165
|
-
return { ownership, dom };
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
function ownerOf(el, rootEl, layoutAttribute) {
|
|
169
|
-
for (let ancestor = el; ancestor && ancestor !== rootEl; ancestor = ancestor.parentElement) {
|
|
170
|
-
if (marked(ancestor)) { return 'grammar'; }
|
|
171
|
-
if (ancestor.hasAttribute(layoutAttribute)) { return 'layout'; }
|
|
172
|
-
}
|
|
173
|
-
return null;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
function ownershipLabel(el, owner) {
|
|
177
|
-
return el.tagName.toLowerCase() + (typeof el.className === 'string' && el.className ? '.' + el.className.trim().split(/\s+/).slice(0, 3).join('.') : '') + (el.textContent.trim() ? ' "' + el.textContent.trim().slice(0, 40) + '"' : '') + ` (${owner ?? 'no owner'})`;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
/** The expression that declares `functions` and returns what `entry` makes of `arg` (JSON data) in the page. */
|
|
181
|
-
function pageScript(entry, functions, arg) {
|
|
182
|
-
return `(() => {\n${functions.map(String).join('\n')}\nreturn ${entry.name}(${JSON.stringify(arg)});\n})()`;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
/** What the page measures: `arg` is {generic, exemptSelector, layoutAttr}. */
|
|
186
|
-
export function measureInPage(page, arg) {
|
|
187
|
-
return page.evaluate(pageScript(measurePage, [measurePage, visible, measureFonts, measureOverflow, measureAccentExempt, measureArtwork, tagOf, partIn, measureAnatomy, measureAlert, measureMeter, marked, paints, measureOwnership, ownerOf, ownershipLabel], arg));
|
|
188
|
-
}
|