@adia-ai/adia-ui-forge 0.8.56 → 0.8.57
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/.claude-plugin/plugin.json +3 -2
- package/.codex-plugin/plugin.json +1 -1
- package/CHANGELOG.md +16 -0
- package/README.md +1 -1
- package/commands/demo-audit.md +1 -1
- package/commands/gen-ui-review.md +1 -1
- package/commands/package-release.md +1 -1
- package/commands/site-deployment.md +1 -1
- package/package.json +1 -1
- package/plugin.yaml +1 -1
- package/prompts/demo-audit.md +1 -1
- package/prompts/gen-ui-review.md +1 -1
- package/prompts/package-release.md +1 -1
- package/prompts/site-deployment.md +1 -1
- package/scripts/forge-lint.mjs +168 -0
- package/scripts/lint-rules.generated.mjs +1700 -0
- package/scripts/site-postwrite-derivation-gate +23 -127
- package/skills/a2ui-maintenance/references/pipeline-overview.md +12 -10
- package/skills/demo-audit/references/visual-probe-triage.md +4 -1
- package/skills/gen-ui-review/SKILL.md +4 -1
- package/skills/gen-ui-review/references/loop-protocol.md +6 -5
- package/skills/package-release/references/changelog-discipline.md +6 -3
- package/skills/package-release/references/cut-procedure.md +15 -13
- package/skills/package-release/references/gates-catalog.md +5 -2
- package/skills/package-release/references/recovery-paths.md +6 -3
- package/skills/package-release/scripts/gate-roster.mjs +10 -7
- package/skills/primitive-authoring/references/common-gotchas.md +6 -6
- package/skills/primitive-authoring/references/yaml-contract.md +27 -14
- package/skills/site-docs-authoring/SKILL.md +7 -9
- package/hooks/hooks.json +0 -44
- package/scripts/forge-lint +0 -315
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "adia-ui-kit-forge",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.57",
|
|
4
4
|
"description": "Maintain the adia-ui (@adia-ai) framework itself \u2014 author primitives and shells, run the A2UI generation pipeline and its corpus, review gen-UI quality, sweep QA, cut releases, deploy. The maintainer counterpart to adia-ui-kit-factory (the consumer/app-author plugin).",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Kim",
|
|
@@ -16,5 +16,6 @@
|
|
|
16
16
|
"framework-authoring",
|
|
17
17
|
"release-engineering",
|
|
18
18
|
"maintainer"
|
|
19
|
-
]
|
|
19
|
+
],
|
|
20
|
+
"hooks": "./adapters/claude/hooks.json"
|
|
20
21
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "adia-ui-kit-forge",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.57",
|
|
4
4
|
"description": "Maintain the adia-ui (@adia-ai) framework itself — author primitives and shells, run the A2UI generation pipeline and its corpus, review gen-UI quality, sweep QA, cut releases, deploy. The maintainer counterpart to adia-ui-kit-factory (the consumer/app-author plugin).",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Kim",
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Changelog — adia-ui-kit-forge
|
|
2
2
|
|
|
3
|
+
## [0.8.57] — 2026-09-01
|
|
4
|
+
|
|
5
|
+
### Removed
|
|
6
|
+
- **`package-release`'s release-roster gate 19 (`verify:site-a2ui`) retired with the site-a2ui mechanism itself (ADR-0072 Decision 2, gh#2410 active drain).** `gate-roster.mjs` drops the gate 19 entry (33 gates → 32; gate number left unassigned rather than renumbering 20+, per the roster's own no-renumber discipline for gate identities cited in shipped release notes); `cut-procedure.md` §3.1's transcription and its `.examples.html`-generator trigger prose updated to match. The `site-postwrite-derivation-gate` hook's fragment-staleness half (the site-a2ui regeneration nudge) is removed; its sitemap-staleness half (`site/sitemap.json` → `build:llms`/`build:patterns-index`), unrelated to site-a2ui, is unchanged. `a2ui-maintenance`'s `pipeline-overview.md` and `site-docs-authoring`'s `SKILL.md`/`primitive-authoring`'s `yaml-contract.md` no longer instruct regenerating a compiled A2UI artifact that no longer exists.
|
|
7
|
+
|
|
8
|
+
### Fixed
|
|
9
|
+
- **Plugin passes `author-cross-harness-plugins`'s cross-harness validator, modulo one documented, permanent, irreconcilable exception (gh#2668).** `${CLAUDE_PLUGIN_ROOT}`/`$ARGUMENTS`/the `` !`command` `` auto-injection syntax removed from shared `gen-ui-review/SKILL.md`, the four `commands/*.md` bodies, and their linked reference docs (`demo-audit/references/visual-probe-triage.md`, `package-release/references/{cut-procedure,changelog-discipline,gates-catalog,recovery-paths}.md`, `gen-ui-review/references/loop-protocol.md`) — portable `<plugin-root>` prose / "named in the current request" phrasing throughout; command frontmatter, including `package-release`/`site-deployment`'s deliberate `disable-model-invocation: true` (AGENTS.md-ruled), is untouched since the validator never scans command frontmatter. `hooks/hooks.json` moved to `adapters/claude/hooks.json` (unchanged, `${CLAUDE_PLUGIN_ROOT}`-based); new root `hooks.json` mirrors the same event/matcher/action signature with monorepo-root-relative command paths for Codex's convention-discovered hook surface — an unverified-but-honest fallback documented in `HARNESS-NOTES.md`. **The one exception:** every skill's `disable-model-invocation:`/`user-invocable:` frontmatter — required, explicitly, by this repo's own blocking `check:skills` gate (`plugin-estate-v2.md`'s species-pinning charter) — collides head-on with the validator's opposite requirement on the exact same file, and Codex is hard-pinned to read root `skills/` directly (no adapter escape). The repo's own charter is not weakened; `npm run check:cross-harness-plugins` (`scripts/verify/check-cross-harness-plugins.mjs`, wrapping the vendored `scripts/verify/vendor/validate_cross_harness.py`) treats only this one exact finding shape as accepted (logged as INFO) and still fails on every other portability class. Full reasoning in `HARNESS-NOTES.md`.
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
- **`forge-lint` rewritten as a thin Node CLI (`forge-lint.mjs`) consuming a shared generated lint-rule bank, retiring the Python script (gh#2638, LLD-0016, ADR-0099).** The framework-authoring structural checks (SHADOW-DOM, SLOTTED, RAW-COLOR, RAW-PX, SCOPE-EXTENT, DEAD-FONT-TOKEN, BOOL-DEFAULT-TRUE, ATTR-TYPO, NATIVE-PRIMITIVE, LEGACY-SHELL, MISSING-SCOPE) now live as individually-fixtured rule modules under `scripts/lint/rules/` in the repo root, built into `scripts/lint/generated/rule-bank.mjs` and vendored byte-identically into this plugin, `adia-ui-factory`, and the gen-ui MCP's factory vendor tree (`npm run build:lint-rules`, freshness gated by `npm run check:lint-rules-fresh`). `hooks/hooks.json`'s PostToolUse command changes from `python3 ".../forge-lint" --hook` to `node ".../forge-lint.mjs" --hook`; the CLI contract, exit codes, and PostToolUse never-block invariant are unchanged. `npm run check:lint-rules-selftest` proves finding-parity against the retired script's own `selftest()` fixtures. New `npm run lint` (`scripts/lint/lint-repo.mjs`) runs the full bank against every git-tracked lintable file — standalone for now, not yet wired into `npm run check` (a first repo-wide run surfaced 330 error-severity pre-existing findings that need triage first — gh#2646). The suppression comment (`/* adia-lint-disable <rule-id> — <reason> */`) is now load-bearing, not cosmetic: it drops the matching finding, and a bare disable with no reason is itself a finding (`SUPPRESS-NO-REASON`).
|
|
13
|
+
|
|
14
|
+
### Maintenance
|
|
15
|
+
- **`.claude-plugin/plugin.json` version bump** — moves in lockstep with package.json (the `/plugin update` cache key).
|
|
16
|
+
- **`.codex-plugin/` touched in this release window** (1 file(s), e.g. `.codex-plugin/plugin.json`) — carried by the entries above.
|
|
17
|
+
- **`prompts/` touched in this release window** (4 file(s), e.g. `prompts/demo-audit.md`) — carried by the entries above.
|
|
18
|
+
|
|
3
19
|
## [0.8.56] — 2026-08-31
|
|
4
20
|
|
|
5
21
|
### Added
|
package/README.md
CHANGED
|
@@ -36,7 +36,7 @@ same decomposition scoped to A2UI catalog/tier cohesion, never touches corpus or
|
|
|
36
36
|
`framework-checker` (fable, **no Write/Edit** — read-only corpus-wide cohesion
|
|
37
37
|
audit: color tokens, typography, state naming, attrs-as-api, implementation patterns).
|
|
38
38
|
|
|
39
|
-
## Hooks (5 — `
|
|
39
|
+
## Hooks (5 — `adapters/claude/hooks.json` is the roster; `check:plugin-count-claims` guards this header count)
|
|
40
40
|
|
|
41
41
|
`sidecar-prewrite-guard` (PreToolUse deny on hand-edits to generated
|
|
42
42
|
artifacts) · `release-pretag-docs-gate` (PreToolUse: release-tagging Bash
|
package/commands/demo-audit.md
CHANGED
|
@@ -6,7 +6,7 @@ disable-model-invocation: false
|
|
|
6
6
|
user-invocable: true
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
-
Run a dogfood sweep.
|
|
9
|
+
Run a dogfood sweep against the mode/surface named in the current request (all, if unscoped).
|
|
10
10
|
|
|
11
11
|
Invoke **`demo-audit`**; run the requested modes (all, if unscoped), then
|
|
12
12
|
triage findings per its severity contract. Detection is script-driven; the
|
|
@@ -6,7 +6,7 @@ disable-model-invocation: false
|
|
|
6
6
|
user-invocable: true
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
-
Run a gen-UI review cycle.
|
|
9
|
+
Run a gen-UI review cycle, scoped to the prompt subset named in the current request (if any).
|
|
10
10
|
|
|
11
11
|
Invoke **`gen-ui-review`** and run its loop: derive ideal → decompose actual
|
|
12
12
|
→ score → root-cause → plan. Corpus fixes route to `a2ui-maintenance`.
|
|
@@ -6,7 +6,7 @@ disable-model-invocation: true
|
|
|
6
6
|
user-invocable: true
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
-
Run a release.
|
|
9
|
+
Run a release for the version/package named in the current request.
|
|
10
10
|
|
|
11
11
|
Invoke **`package-release`** and run its cut procedure end-to-end: pre-flight
|
|
12
12
|
gates → cut → tag → publish → deploy → notes. This command's invocation is
|
|
@@ -6,7 +6,7 @@ disable-model-invocation: true
|
|
|
6
6
|
user-invocable: true
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
-
Deploy or diagnose
|
|
9
|
+
Deploy or diagnose the exe.dev host/service named in the current request.
|
|
10
10
|
|
|
11
11
|
Invoke **`site-deployment`** and follow its playbook for the named host; verify
|
|
12
12
|
against the live endpoint before reporting done.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adia-ai/adia-ui-forge",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.57",
|
|
4
4
|
"description": "Maintain the adia-ui (@adia-ai) framework itself — author primitives and shells, run the A2UI generation pipeline and its corpus, review gen-UI quality, sweep QA, cut releases, deploy. The maintainer counterpart to adia-factory (the consumer/app-author plugin).",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"adia-ui",
|
package/plugin.yaml
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
name: "adia-ui-kit-forge"
|
|
2
|
-
version: "0.8.
|
|
2
|
+
version: "0.8.57"
|
|
3
3
|
description: "Maintain the adia-ui (@adia-ai) framework itself — author primitives and shells, run the A2UI generation pipeline and its corpus, review gen-UI quality, sweep QA, cut releases, deploy. The maintainer counterpart to adia-ui-kit-factory (the consumer/app-author plugin)."
|
|
4
4
|
manifest_version: 1
|
package/prompts/demo-audit.md
CHANGED
|
@@ -3,7 +3,7 @@ description: "Run the multi-mode QA sweep (visual probe, static QA, typo, native
|
|
|
3
3
|
argument-hint: "[mode or surface]"
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
Run a dogfood sweep.
|
|
6
|
+
Run a dogfood sweep against the mode/surface named in the current request (all, if unscoped).
|
|
7
7
|
|
|
8
8
|
Invoke **`demo-audit`**; run the requested modes (all, if unscoped), then
|
|
9
9
|
triage findings per its severity contract. Detection is script-driven; the
|
package/prompts/gen-ui-review.md
CHANGED
|
@@ -3,7 +3,7 @@ description: "Run the closed-loop gen-UI quality review — score gallery output
|
|
|
3
3
|
argument-hint: "[prompt subset]"
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
Run a gen-UI review cycle.
|
|
6
|
+
Run a gen-UI review cycle, scoped to the prompt subset named in the current request (if any).
|
|
7
7
|
|
|
8
8
|
Invoke **`gen-ui-review`** and run its loop: derive ideal → decompose actual
|
|
9
9
|
→ score → root-cause → plan. Corpus fixes route to `a2ui-maintenance`.
|
|
@@ -3,7 +3,7 @@ description: "Cut, tag, publish, and deploy an @adia-ai release (lockstep roster
|
|
|
3
3
|
argument-hint: "[version or package]"
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
Run a release.
|
|
6
|
+
Run a release for the version/package named in the current request.
|
|
7
7
|
|
|
8
8
|
Invoke **`package-release`** and run its cut procedure end-to-end: pre-flight
|
|
9
9
|
gates → cut → tag → publish → deploy → notes. This command's invocation is
|
|
@@ -3,7 +3,7 @@ description: "Deploy a service or dist to the exe.dev VMs (ui-kit.exe.xyz and si
|
|
|
3
3
|
argument-hint: "[host or service]"
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
Deploy or diagnose
|
|
6
|
+
Deploy or diagnose the exe.dev host/service named in the current request.
|
|
7
7
|
|
|
8
8
|
Invoke **`site-deployment`** and follow its playbook for the named host; verify
|
|
9
9
|
against the live endpoint before reporting done.
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* forge-lint.mjs — thin Node CLI consuming the vendored generated rule bank
|
|
4
|
+
* (lint-rules.generated.mjs, LLD-0016 §C5) for the adia-ui FRAMEWORK source
|
|
5
|
+
* (maintainer slice): the same structural-smell checks forge-lint (Python)
|
|
6
|
+
* ran, now run: 'shared' + 'forge-only' scope rules only — no consumer/app
|
|
7
|
+
* traps (SSR, hardcoded overlay open, LLM key, genui). See ADR-0099 for why
|
|
8
|
+
* this replaced the Python script as the hooks.json entry point.
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* forge-lint.mjs <file>... lint files; exit 1 if any error-severity finding, else 0
|
|
12
|
+
* forge-lint.mjs - lint stdin as a generic source file
|
|
13
|
+
* forge-lint.mjs --hook PostToolUse hook mode: read event JSON on stdin, lint the
|
|
14
|
+
* written source file, print advisory findings, ALWAYS exit 0
|
|
15
|
+
* forge-lint.mjs selftest run the inherited parity fixtures + the hook exit-0 invariant
|
|
16
|
+
*/
|
|
17
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
18
|
+
import { dirname, join } from 'node:path';
|
|
19
|
+
import { fileURLToPath } from 'node:url';
|
|
20
|
+
|
|
21
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
22
|
+
const { makeLinter, ext, LINT_EXT, RULES } = await import(join(__dirname, 'lint-rules.generated.mjs'));
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Repo-root-relative parity fixtures are only reachable when this CLI is
|
|
26
|
+
* running from inside the monorepo checkout (the plugin's own source tree,
|
|
27
|
+
* not a packed/vendored install elsewhere) — walk up from this file looking
|
|
28
|
+
* for scripts/lint/rule-bank.selftest.mjs rather than a fixed `../..` depth,
|
|
29
|
+
* since this same CLI shape is vendored to more than one directory depth
|
|
30
|
+
* (ADR-0099). A vendored copy outside the monorepo has no fixtures to find
|
|
31
|
+
* and `selftest` reports that plainly instead of crashing.
|
|
32
|
+
*/
|
|
33
|
+
function findParityFixtures() {
|
|
34
|
+
let dir = __dirname;
|
|
35
|
+
for (let i = 0; i < 8; i += 1) {
|
|
36
|
+
const candidate = join(dir, 'scripts/lint/rule-bank.selftest.mjs');
|
|
37
|
+
if (existsSync(candidate)) return candidate;
|
|
38
|
+
dir = join(dir, '..');
|
|
39
|
+
}
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const SCOPE_ANCHOR = ['packages/web-components/', 'packages/web-modules/'];
|
|
44
|
+
const { lintText } = makeLinter(RULES, { scopes: ['shared', 'forge-only'] });
|
|
45
|
+
|
|
46
|
+
function render(path, findings) {
|
|
47
|
+
const out = [`forge-lint: ${findings.length} structural smell(s) in ${path || '<stdin>'}`];
|
|
48
|
+
for (const f of findings) {
|
|
49
|
+
out.push(` [${f.name}] line ${f.line}: ${f.snippet}`);
|
|
50
|
+
out.push(` → ${f.why}`);
|
|
51
|
+
}
|
|
52
|
+
return out.join('\n');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function lintPath(path) {
|
|
56
|
+
try {
|
|
57
|
+
return lintText(readFileSync(path, 'utf8'), path);
|
|
58
|
+
} catch (err) {
|
|
59
|
+
return [{ name: 'READ-ERROR', line: 1, snippet: path.slice(0, 90), why: `failed to read file: ${err.message}` }];
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async function readStdin() {
|
|
64
|
+
const chunks = [];
|
|
65
|
+
for await (const c of process.stdin) chunks.push(c);
|
|
66
|
+
return Buffer.concat(chunks).toString('utf8');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function hook() {
|
|
70
|
+
let event;
|
|
71
|
+
try {
|
|
72
|
+
event = JSON.parse(await readStdin());
|
|
73
|
+
} catch {
|
|
74
|
+
return 0;
|
|
75
|
+
}
|
|
76
|
+
const ti = event.tool_input || {};
|
|
77
|
+
const path = ti.file_path || '';
|
|
78
|
+
const norm = path.replace(/\\/g, '/');
|
|
79
|
+
if (!SCOPE_ANCHOR.some((a) => norm.includes(a))) return 0;
|
|
80
|
+
if (!LINT_EXT.includes(ext(path))) return 0;
|
|
81
|
+
let text = ti.content;
|
|
82
|
+
if (text === undefined || text === null) {
|
|
83
|
+
if (!existsSync(path)) return 0;
|
|
84
|
+
try {
|
|
85
|
+
text = readFileSync(path, 'utf8');
|
|
86
|
+
} catch {
|
|
87
|
+
return 0;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
const findings = lintText(text, path);
|
|
91
|
+
if (findings.length) {
|
|
92
|
+
const context = render(path, findings)
|
|
93
|
+
+ '\n (forge-lint advisory — adia-ui framework authoring smells; the skills own the judgment)';
|
|
94
|
+
process.stdout.write(JSON.stringify({
|
|
95
|
+
hookSpecificOutput: { hookEventName: 'PostToolUse', additionalContext: context },
|
|
96
|
+
}) + '\n');
|
|
97
|
+
}
|
|
98
|
+
return 0; // NEVER block
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async function selftest() {
|
|
102
|
+
const fixturesPath = findParityFixtures();
|
|
103
|
+
if (!fixturesPath) {
|
|
104
|
+
console.log('selftest: SKIP — no monorepo checkout found (parity fixtures live in scripts/lint/, not vendored)');
|
|
105
|
+
return 0;
|
|
106
|
+
}
|
|
107
|
+
const { PARITY_CASES } = await import(fixturesPath);
|
|
108
|
+
let ok = true;
|
|
109
|
+
for (const { name, text, expected, scopes } of PARITY_CASES) {
|
|
110
|
+
if (scopes && !(scopes.includes('forge-only'))) continue; // this CLI's own cases only
|
|
111
|
+
const got = new Set(lintText(text, name).map((f) => f.name));
|
|
112
|
+
const missing = [...expected].filter((e) => !got.has(e));
|
|
113
|
+
if (missing.length) {
|
|
114
|
+
ok = false;
|
|
115
|
+
console.error(`selftest: ${name} MISSING ${JSON.stringify(missing)} (got ${JSON.stringify([...got])})`);
|
|
116
|
+
}
|
|
117
|
+
if (expected.size === 0 && got.size) {
|
|
118
|
+
ok = false;
|
|
119
|
+
console.error(`selftest: ${name} expected clean, got ${JSON.stringify([...got])}`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
// never-block invariant
|
|
123
|
+
const savedWrite = process.stdout.write.bind(process.stdout);
|
|
124
|
+
let hookOut = '';
|
|
125
|
+
process.stdout.write = (chunk, ...rest) => { hookOut += chunk; return true; };
|
|
126
|
+
const stdinMock = Buffer.from(JSON.stringify({ tool_input: { file_path: 'packages/web-components/c.css', content: '.a { color:#f00; }' } }));
|
|
127
|
+
const origStdin = process.stdin;
|
|
128
|
+
Object.defineProperty(process, 'stdin', { value: (async function* () { yield stdinMock; })(), configurable: true });
|
|
129
|
+
let rc;
|
|
130
|
+
try {
|
|
131
|
+
rc = await hook();
|
|
132
|
+
} finally {
|
|
133
|
+
process.stdout.write = savedWrite;
|
|
134
|
+
Object.defineProperty(process, 'stdin', { value: origStdin, configurable: true });
|
|
135
|
+
}
|
|
136
|
+
if (rc !== 0) {
|
|
137
|
+
ok = false;
|
|
138
|
+
console.error('selftest: --hook did not exit 0 on smelly input');
|
|
139
|
+
}
|
|
140
|
+
if (rc === 0 && hookOut && !hookOut.includes('"hookSpecificOutput"')) {
|
|
141
|
+
ok = false;
|
|
142
|
+
console.error('selftest: --hook stdout is not structured hookSpecificOutput JSON');
|
|
143
|
+
}
|
|
144
|
+
console.log(ok ? 'selftest: PASS' : 'selftest: FAIL');
|
|
145
|
+
return ok ? 0 : 1;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
async function main(argv) {
|
|
149
|
+
if (argv[0] === 'selftest') return selftest();
|
|
150
|
+
if (argv.includes('--hook')) return hook();
|
|
151
|
+
const args = argv.filter((a) => a === '-' || !a.startsWith('-'));
|
|
152
|
+
if (!args.length) {
|
|
153
|
+
console.error('usage: forge-lint.mjs <file>... | forge-lint.mjs - | forge-lint.mjs --hook | forge-lint.mjs selftest');
|
|
154
|
+
return 2;
|
|
155
|
+
}
|
|
156
|
+
let total = 0;
|
|
157
|
+
for (let path of args) {
|
|
158
|
+
const findings = path === '-' ? lintText(await readStdin(), '<stdin>') : lintPath(path);
|
|
159
|
+
if (path === '-') path = '<stdin>';
|
|
160
|
+
if (findings.length) {
|
|
161
|
+
total += findings.length;
|
|
162
|
+
console.log(render(path, findings));
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return total ? 1 : 0;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
process.exit(await main(process.argv.slice(2)));
|