etymd 0.16.0 → 0.19.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 +83 -0
- package/README.md +28 -5
- package/dist/{approve-K5FFTPA4.js → approve-Y36ZO6IL.js} +3 -3
- package/dist/audit-RONSEYFS.js +11 -0
- package/dist/{brief-DOQ2DQEG.js → brief-ILB6RMSQ.js} +3 -3
- package/dist/{chunk-FKDT4RBP.js → chunk-4IESOWHQ.js} +2 -2
- package/dist/{chunk-EJM47PIF.js → chunk-DLHC7QON.js} +83 -10
- package/dist/{chunk-Y4E6ERCM.js → chunk-EYUW3UJU.js} +59 -11
- package/dist/chunk-EZRK4MV3.js +109 -0
- package/dist/{chunk-OKJ3RGJB.js → chunk-HSRHOVII.js} +113 -117
- package/dist/{chunk-UV3YIBDW.js → chunk-KIPD3N77.js} +11 -6
- package/dist/{chunk-UYCGP5BS.js → chunk-L4KIIKXI.js} +1 -1
- package/dist/{chunk-YQZDYDAK.js → chunk-U7BCBJIH.js} +1 -1
- package/dist/{chunk-ANXGT7V3.js → chunk-ZCOIZVSM.js} +37 -5
- package/dist/cli.js +17 -17
- package/dist/{doctor-X6XAGKV7.js → doctor-EHRADIUZ.js} +6 -6
- package/dist/{fleet-UEFLKIRI.js → fleet-KFF3N2O7.js} +22 -6
- package/dist/{gates-NVWWKMT2.js → gates-UJ23THID.js} +9 -5
- package/dist/{generate-YEARCSHW.js → generate-6IPTCLYG.js} +3 -2
- package/dist/index.d.ts +29 -3
- package/dist/index.js +313 -52
- package/dist/{init-Z5O2WVRJ.js → init-7HWVHZMF.js} +5 -4
- package/dist/{premise-4QRU7LRI.js → premise-U6JIB2QG.js} +9 -5
- package/dist/{propose-IKE4CUIM.js → propose-NZMEPZIL.js} +6 -6
- package/dist/{scan-H3NF5IV5.js → scan-DLKYG5BB.js} +3 -3
- package/dist/scan-V53PGAQ6.js +5 -0
- package/dist/{screen-E4FC7W5N.js → screen-S736DXQ5.js} +56 -24
- package/package.json +2 -2
- package/dist/audit-HBNXNGD6.js +0 -11
- package/dist/scan-64Z6SNYF.js +0 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,88 @@
|
|
|
1
1
|
# etymd
|
|
2
2
|
|
|
3
|
+
## 0.19.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 29c7b31: The Claude Code pointer check follows Claude Code 2.1.277, which reads `AGENTS.md` when a
|
|
8
|
+
directory has no `CLAUDE.md`. A repo with `AGENTS.md` alone now passes and `fleet add` registers
|
|
9
|
+
it; it is reported (`claude-pointer-missing`, tier gap) only when the installed Claude Code is
|
|
10
|
+
older than 2.1.277, and `fleet add` prints a note instead of refusing. A `CLAUDE.md` that exists
|
|
11
|
+
without importing `@AGENTS.md` stays a risk and a refusal, because Claude Code reads that file
|
|
12
|
+
instead on every version. The version is read from `claude --version`; `ETYMD_CLAUDE_VERSION`
|
|
13
|
+
pins it, and `none` means no Claude Code on the machine.
|
|
14
|
+
|
|
15
|
+
### Patch Changes
|
|
16
|
+
|
|
17
|
+
- 3dd2683: Dependency advisories cleared inside the declared ranges. `vitest` 4.1.10 → 4.1.11 closes
|
|
18
|
+
GHSA-82fw-gwwq-j7x9 (moderate: path traversal / arbitrary file read through the `@vitest/mocker`
|
|
19
|
+
redirect mock), and `js-yaml` 4.3.1 → 4.3.2 with its transitive 3.15.1 → 3.15.2 closes
|
|
20
|
+
GHSA-2883-xcg3-v3hh (high: `maxTotalMergeKeys` does not limit CPU use for empty merge sources).
|
|
21
|
+
Both are dev-only — the test runner and the changeset tooling — so the published build is
|
|
22
|
+
byte-identical; the lockfile also picks up the range-satisfying refresh those two pull in.
|
|
23
|
+
|
|
24
|
+
One advisory stays open, and deliberately: GHSA-g7r4-m6w7-qqqr (low: `esbuild` allows an arbitrary
|
|
25
|
+
file read when its development server runs on Windows) is fixed in esbuild 0.28.1, while `tsup`
|
|
26
|
+
8.5.1 — the newest release — declares `esbuild: ^0.27.0`, so no in-range version of esbuild carries
|
|
27
|
+
the fix. The only automatic fix on offer is a downgrade to esbuild 0.27.2, which gives up five
|
|
28
|
+
patch releases to dodge a dev-server path this repo never takes: the build runs tsup, never an
|
|
29
|
+
esbuild server. It clears once tsup widens its range.
|
|
30
|
+
|
|
31
|
+
- 7d4e81f: `core.hooksPath` is read the way git reads it: a repo-relative path, an absolute path and a `~`
|
|
32
|
+
path can all name one directory, and the directory is the fact. An absolute path to the tracked
|
|
33
|
+
`.githooks/` now scans as `githooks` (wired) instead of `custom`, and the hooks are read from the
|
|
34
|
+
directory git actually runs — the literal-string comparison had also joined the absolute path
|
|
35
|
+
onto the repo root and looked for hooks at a path that does not exist, so a working pre-push gate
|
|
36
|
+
could read as absent. The fact is recorded repo-relative whenever the directory sits inside the
|
|
37
|
+
worktree, so a committed baseline never carries a machine path; a hooks directory outside the
|
|
38
|
+
repo stays absolute and is disclosed as `custom`.
|
|
39
|
+
|
|
40
|
+
## 0.18.0
|
|
41
|
+
|
|
42
|
+
### Minor Changes
|
|
43
|
+
|
|
44
|
+
- 41b91f6: Generated pre-push hooks preserve long filenames, whitespace, quotes, and leading
|
|
45
|
+
hyphens during shell-script discovery and checking, and a name carrying shell metacharacters
|
|
46
|
+
reaches the scan as data, never as code. Discovery fails closed instead of reporting success
|
|
47
|
+
with incomplete coverage: failed enumeration blocks, and a tracked regular file that exists
|
|
48
|
+
but cannot be read blocks, naming the file. Tracked paths with nothing readable behind them —
|
|
49
|
+
submodule entries, dangling symlinks, files deleted from the worktree while still tracked —
|
|
50
|
+
are counted and disclosed as skipped rather than blocking, so a submodule can no longer wedge
|
|
51
|
+
every push. Shebang reads are bounded to the first 4 KiB of the first line, so a large binary
|
|
52
|
+
cannot be copied into the gate's scratch file on every push and a shebang embedded deeper in
|
|
53
|
+
a document no longer drags the whole document into the checker. Regenerate existing hooks
|
|
54
|
+
with `etymd gates --yes` to apply the correction.
|
|
55
|
+
- 4a92435: The shebang classifier ships as a tracked, shebanged helper beside the hook
|
|
56
|
+
(`.githooks/discover-shell-scripts.sh`), so the scan it implements finds and checks it too —
|
|
57
|
+
the gate covers its own classifier, and the hook's most intricate code is lintable on its
|
|
58
|
+
own lines.
|
|
59
|
+
|
|
60
|
+
## 0.17.0
|
|
61
|
+
|
|
62
|
+
### Minor Changes
|
|
63
|
+
|
|
64
|
+
- The content screen's allow file accepts a `generated <path-regex>` record: a path declared
|
|
65
|
+
as regenerated data is still read and still screened, but vocabulary-class patterns drop
|
|
66
|
+
for it — secret-class patterns and the machine-path check stay — and the exempted paths
|
|
67
|
+
are named in the output, so a clean run never reads as fully screened. Provenance
|
|
68
|
+
(reason, date, author) is required.
|
|
69
|
+
|
|
70
|
+
### Patch Changes
|
|
71
|
+
|
|
72
|
+
- 2bceef1: The fleet's gate-drift check now derives the pre-push audit tier the same way `etymd gates`
|
|
73
|
+
does. In a repo where no risk-tier rule can fire (no package manifest, no state doc) the
|
|
74
|
+
generator lowers the audit line to `--fail-on gap`; the drift comparison planned with the raw
|
|
75
|
+
config tier (`risk`), so its expectation permanently differed from the hook the generator
|
|
76
|
+
itself writes — a `gate-stale` finding on every repo of that shape, unclearable by the action
|
|
77
|
+
it names. The derivation now lives in `planWorkflow` itself, where the hook is built, so every
|
|
78
|
+
caller (the drift check, `etymd init`, the programmatic surface) generates identical bytes; a
|
|
79
|
+
tier the repo pinned in config is still never lowered.
|
|
80
|
+
- 7221fea: The content screen's machine-path rule now fires only at a path boundary (line start,
|
|
81
|
+
whitespace, a quote, `=`, `(`, `[` or `:`). A home path inside a longer token or a URL —
|
|
82
|
+
a test fixture's temp root (`$ROOT/home/‹x›/`), a docs URL with a home segment — is not a
|
|
83
|
+
machine path, and no longer prints the same advisory line on every push. A bare
|
|
84
|
+
`/Users/<name>/…` or `/home/<name>/…` still hits.
|
|
85
|
+
|
|
3
86
|
## 0.16.0
|
|
4
87
|
|
|
5
88
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -174,7 +174,7 @@ personal and guarded repos. See [the fleet manifest](#the-fleet-manifest-experim
|
|
|
174
174
|
| Command | What it does |
|
|
175
175
|
| -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
176
176
|
| `etymd audit` | Verify every claim; ranked findings (risk → gap → polish) + ledger diff. `--lens`, `--truth`, `--json`, `--no-ledger`, `--fail-on <tier>`. |
|
|
177
|
-
| `etymd init` | Onboard: approve the committed baseline; scaffold a minimal AGENTS.md **only if missing
|
|
177
|
+
| `etymd init` | Onboard: approve the committed baseline; scaffold a minimal AGENTS.md **only if missing**, with its `CLAUDE.md` pointer. Never overwrites. |
|
|
178
178
|
| `etymd doctor` | Alias for `audit --truth`. |
|
|
179
179
|
| `etymd context` | The economy view: per-file always-loaded footprint + extraction candidates. |
|
|
180
180
|
| `etymd gates` | Install local git-hook gates (pre-commit / commit-msg / pre-push, plus a publish screen where something ships) built from your own check scripts — and from the repo's shell surface, where it has one. |
|
|
@@ -188,7 +188,7 @@ personal and guarded repos. See [the fleet manifest](#the-fleet-manifest-experim
|
|
|
188
188
|
| `etymd accept` | `accept <id>` — record a finding as accepted reality; visible in the ledger, out of the report. |
|
|
189
189
|
| `etymd fleet` | Sweep every project in a fleet manifest: read-only per-repo audits + manifest/wall checks. `--manifest`, `--only`, `--profile`, `--truth`, `--persist-ledgers`, `--json`, `--fail-on`. |
|
|
190
190
|
| `etymd fleet check` | Validate the manifest pair alone (no lenses): dangling mappings, duplicate names, privacy leaks, undeclared trust, machine paths. Non-zero exit on any finding. |
|
|
191
|
-
| `etymd fleet add` | `add <dir>` — register a project: scans it, asks for what no scan can derive, and refuses to write an entry missing a mandatory field. `--name`, `--kind`, `--profile`, `--trust`, `-y`.
|
|
191
|
+
| `etymd fleet add` | `add <dir>` — register a project: scans it, asks for what no scan can derive, and refuses to write an entry missing a mandatory field, or a repo whose `AGENTS.md` Claude Code cannot see. `--name`, `--kind`, `--profile`, `--trust`, `-y`. |
|
|
192
192
|
| `etymd fleet board` | Render the fleet board: every project's `MILESTONES.md` (contract key `milestones`, shape-checked by the sweep) plus a ranked initiatives table on one page. `--initiatives <file>`, `--out <file>`, `--json`. |
|
|
193
193
|
| `etymd propose` | Score the sweep's improvement findings + recurring classes against a fleet-authored rubric — stable `proposal/1` records, read-only, deterministic, guarded entries excluded. `--rubric <file>` (required), `--manifest <file>` or `--from <fleet.json>`, `--json`. |
|
|
194
194
|
| `etymd fleet dismiss` / `accept` | `<name> <id>` — resolve a project's finding from any cwd; guarded findings persist beside the manifest, never in the guarded worktree. |
|
|
@@ -375,6 +375,24 @@ missing a field is reported and does not apply: an exemption is a hole in the ga
|
|
|
375
375
|
nobody signed cannot be audited later. The file is read from the repo being screened, never a
|
|
376
376
|
shared location, and it screens itself out (it necessarily contains every string it exempts).
|
|
377
377
|
|
|
378
|
+
The file carries a second record kind, for paths rather than lines:
|
|
379
|
+
|
|
380
|
+
```
|
|
381
|
+
generated ^src/data/corpus\.json$
|
|
382
|
+
reason public-domain corpus, regenerated by scripts/corpus each January
|
|
383
|
+
date 2026-09-04
|
|
384
|
+
author someone
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
A `generated` path is data a pipeline rebuilds — a public-domain corpus, generated fixtures —
|
|
388
|
+
and period English trips credential vocabulary (`passkey`, `secret`) on a schedule, training
|
|
389
|
+
exactly the `--no-verify` habit the screen exists to prevent. The file is still read and still
|
|
390
|
+
screened: only vocabulary-class patterns drop for it, while secret-class patterns and the
|
|
391
|
+
machine-path check stay — a generated file is precisely where a real credential would be least
|
|
392
|
+
visible. Provenance is unconditional (a path is never the repo naming itself), and the paths
|
|
393
|
+
that took the exemption are named in the output, so a clean run never reads as fully screened
|
|
394
|
+
where it was screened narrower.
|
|
395
|
+
|
|
378
396
|
### The commit subject, if you ask for it
|
|
379
397
|
|
|
380
398
|
Off unless you turn it on:
|
|
@@ -502,7 +520,11 @@ Two fields the scan can never derive, so the manifest must declare them:
|
|
|
502
520
|
|
|
503
521
|
`etymd fleet add <dir>` is the gate that keeps both true: it scans the project, prompts for what
|
|
504
522
|
no scan can derive, and **refuses to write an incomplete entry**. Non-interactive runs (`--yes`,
|
|
505
|
-
CI) must pass every mandatory value as a flag — there is deliberately no default.
|
|
523
|
+
CI) must pass every mandatory value as a flag — there is deliberately no default. It also refuses
|
|
524
|
+
a repo whose `CLAUDE.md` hides its `AGENTS.md`: Claude Code reads `CLAUDE.md` when one exists and
|
|
525
|
+
falls back to `AGENTS.md` (from 2.1.277) only when none does, so a `CLAUDE.md` must import
|
|
526
|
+
`@AGENTS.md` or be a symlink to it — the refusal prints the fix. A repo with `AGENTS.md` alone
|
|
527
|
+
registers; on a Claude Code older than 2.1.277 it gets a note, since that version cannot see it.
|
|
506
528
|
|
|
507
529
|
How the sweep behaves:
|
|
508
530
|
|
|
@@ -525,8 +547,9 @@ How the sweep behaves:
|
|
|
525
547
|
- **Wall checks.** Guarded contract files found inside a guarded worktree, unregistered checkouts
|
|
526
548
|
under the fleet root whose remotes match `guardedHosts`, tracked `/Users/` paths in the manifest
|
|
527
549
|
repo, private **needles** — the identifiers the local file holds (labels, dir names, hosts) —
|
|
528
|
-
inside `trust: "public-repo"` entries,
|
|
529
|
-
|
|
550
|
+
inside `trust: "public-repo"` entries, guarded-host commit emails on personal entries, and
|
|
551
|
+
repos whose `AGENTS.md` no `CLAUDE.md` pointer or symlink makes visible to Claude Code
|
|
552
|
+
(`claude-pointer-missing`) — each a risk finding; each check that cannot run is disclosed.
|
|
530
553
|
- **No global pointer.** `--manifest` is required unless the cwd holds `registry.json` — there
|
|
531
554
|
is deliberately no env var and no home-directory pointer.
|
|
532
555
|
- **Milestones and the fleet board.** A project declares its plan in one file — `MILESTONES.md`,
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { section, theme, print, renderBaselineDrift, glyph } from './chunk-HI7NWPRA.js';
|
|
3
|
-
import { scanProject } from './chunk-
|
|
4
|
-
import { VERSION } from './chunk-
|
|
3
|
+
import { scanProject } from './chunk-DLHC7QON.js';
|
|
4
|
+
import { VERSION } from './chunk-L4KIIKXI.js';
|
|
5
5
|
import { readBaseline, summarizeBaselineDrift, isDriftEmpty, writeBaseline, deriveProfile } from './chunk-P6ATKV2R.js';
|
|
6
|
-
import { PACK_VERSION } from './chunk-
|
|
6
|
+
import { PACK_VERSION } from './chunk-U7BCBJIH.js';
|
|
7
7
|
import './chunk-4VPBP6K6.js';
|
|
8
8
|
|
|
9
9
|
// src/commands/approve.ts
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
export { run } from './chunk-4IESOWHQ.js';
|
|
3
|
+
import './chunk-KIPD3N77.js';
|
|
4
|
+
import './chunk-EYUW3UJU.js';
|
|
5
|
+
import './chunk-3E2IPCRY.js';
|
|
6
|
+
import './chunk-HI7NWPRA.js';
|
|
7
|
+
import './chunk-DLHC7QON.js';
|
|
8
|
+
import './chunk-L4KIIKXI.js';
|
|
9
|
+
import './chunk-P6ATKV2R.js';
|
|
10
|
+
import './chunk-U7BCBJIH.js';
|
|
11
|
+
import './chunk-4VPBP6K6.js';
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { print, theme } from './chunk-HI7NWPRA.js';
|
|
3
|
-
import { scanProject } from './chunk-
|
|
4
|
-
import './chunk-
|
|
3
|
+
import { scanProject } from './chunk-DLHC7QON.js';
|
|
4
|
+
import './chunk-L4KIIKXI.js';
|
|
5
5
|
import { ETYMD_DIR } from './chunk-P6ATKV2R.js';
|
|
6
|
-
import './chunk-
|
|
6
|
+
import './chunk-U7BCBJIH.js';
|
|
7
7
|
import './chunk-4VPBP6K6.js';
|
|
8
8
|
import { promises } from 'fs';
|
|
9
9
|
import path from 'path';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { runAudit } from './chunk-
|
|
3
|
-
import { parseFailOnTier, meetsFailOn } from './chunk-
|
|
2
|
+
import { runAudit } from './chunk-KIPD3N77.js';
|
|
3
|
+
import { parseFailOnTier, meetsFailOn } from './chunk-EYUW3UJU.js';
|
|
4
4
|
import { print, section, theme, renderLensCoverage, renderFindings, renderLedgerDiff } from './chunk-HI7NWPRA.js';
|
|
5
5
|
|
|
6
6
|
// src/commands/audit.ts
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { VERSION } from './chunk-
|
|
3
|
-
import { PACK_VERSION } from './chunk-
|
|
4
|
-
import { normalizeRelPath, isDirectory,
|
|
2
|
+
import { VERSION } from './chunk-L4KIIKXI.js';
|
|
3
|
+
import { PACK_VERSION } from './chunk-U7BCBJIH.js';
|
|
4
|
+
import { normalizeRelPath, isDirectory, pathExists, readText, readJson, git, matchesAnyGlob } from './chunk-4VPBP6K6.js';
|
|
5
5
|
import path from 'path';
|
|
6
|
+
import { execFile } from 'child_process';
|
|
6
7
|
import { promises } from 'fs';
|
|
8
|
+
import { promisify } from 'util';
|
|
7
9
|
|
|
8
10
|
var IGNORED_DIRS = /* @__PURE__ */ new Set([
|
|
9
11
|
"node_modules",
|
|
@@ -265,10 +267,17 @@ async function hasLintStagedConfig(root, pkg) {
|
|
|
265
267
|
}
|
|
266
268
|
return false;
|
|
267
269
|
}
|
|
270
|
+
function normalizeHooksPath(root, raw) {
|
|
271
|
+
const abs = path.resolve(root, raw);
|
|
272
|
+
const rel = path.relative(root, abs);
|
|
273
|
+
const outside = path.isAbsolute(rel) || rel === ".." || rel.startsWith(`..${path.sep}`);
|
|
274
|
+
return outside ? abs : normalizeRelPath(rel) || ".";
|
|
275
|
+
}
|
|
268
276
|
async function detectHooks(root, hooksPath, pkg) {
|
|
269
277
|
const lintStaged = await hasLintStagedConfig(root, pkg);
|
|
270
278
|
if (hooksPath) {
|
|
271
|
-
|
|
279
|
+
const dir = normalizeHooksPath(root, hooksPath);
|
|
280
|
+
if (dir === ".husky/_") {
|
|
272
281
|
const base2 = path.join(root, ".husky");
|
|
273
282
|
return {
|
|
274
283
|
source: "husky",
|
|
@@ -279,10 +288,10 @@ async function detectHooks(root, hooksPath, pkg) {
|
|
|
279
288
|
lintStaged
|
|
280
289
|
};
|
|
281
290
|
}
|
|
282
|
-
const base = path.
|
|
291
|
+
const base = path.resolve(root, dir);
|
|
283
292
|
return {
|
|
284
|
-
source:
|
|
285
|
-
dir
|
|
293
|
+
source: dir === ".githooks" ? "githooks" : "custom",
|
|
294
|
+
dir,
|
|
286
295
|
preCommit: await pathExists(path.join(base, "pre-commit")),
|
|
287
296
|
prePush: await pathExists(path.join(base, "pre-push")),
|
|
288
297
|
commitMsg: await pathExists(path.join(base, "commit-msg")),
|
|
@@ -438,6 +447,69 @@ async function detectArtifacts(root) {
|
|
|
438
447
|
});
|
|
439
448
|
return artifacts;
|
|
440
449
|
}
|
|
450
|
+
var ROOT_POINTER_IMPORT_RE = /^\s*@(\.\/)?AGENTS\.md\s*$/m;
|
|
451
|
+
var DOT_CLAUDE_IMPORT_RE = /^\s*@\.\.\/AGENTS\.md\s*$/m;
|
|
452
|
+
var CLAUDE_AGENTS_FALLBACK_VERSION = "2.1.277";
|
|
453
|
+
var pExecFile = promisify(execFile);
|
|
454
|
+
var claudeVersionMemo;
|
|
455
|
+
function detectClaudeCodeVersion() {
|
|
456
|
+
const override = process.env.ETYMD_CLAUDE_VERSION;
|
|
457
|
+
if (override !== void 0) {
|
|
458
|
+
return Promise.resolve(override === "none" || override === "" ? null : override);
|
|
459
|
+
}
|
|
460
|
+
claudeVersionMemo ??= pExecFile("claude", ["--version"], { timeout: 5e3 }).then(({ stdout }) => /(\d+\.\d+\.\d+)/.exec(stdout)?.[1] ?? null).catch(() => null);
|
|
461
|
+
return claudeVersionMemo;
|
|
462
|
+
}
|
|
463
|
+
function versionBefore(a, b) {
|
|
464
|
+
const pa = a.split(".").map(Number);
|
|
465
|
+
const pb = b.split(".").map(Number);
|
|
466
|
+
for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
|
|
467
|
+
const x = pa[i] ?? 0;
|
|
468
|
+
const y = pb[i] ?? 0;
|
|
469
|
+
if (x !== y) return x < y;
|
|
470
|
+
}
|
|
471
|
+
return false;
|
|
472
|
+
}
|
|
473
|
+
async function checkClaudePointer(root, claudeVersion) {
|
|
474
|
+
const agentsAbs = path.join(root, "AGENTS.md");
|
|
475
|
+
const claudeAbs = path.join(root, "CLAUDE.md");
|
|
476
|
+
if (!await pathExists(agentsAbs)) return { ok: true, via: "no-agents" };
|
|
477
|
+
const statOrNone = async (p) => {
|
|
478
|
+
try {
|
|
479
|
+
return await promises.stat(p);
|
|
480
|
+
} catch {
|
|
481
|
+
return null;
|
|
482
|
+
}
|
|
483
|
+
};
|
|
484
|
+
const [agentsSt, claudeSt] = await Promise.all([statOrNone(agentsAbs), statOrNone(claudeAbs)]);
|
|
485
|
+
if (agentsSt && claudeSt && agentsSt.ino !== 0 && agentsSt.ino === claudeSt.ino && agentsSt.dev === claudeSt.dev) {
|
|
486
|
+
return { ok: true, via: "same-file" };
|
|
487
|
+
}
|
|
488
|
+
const rootClaude = await readText(claudeAbs);
|
|
489
|
+
if (rootClaude !== null && ROOT_POINTER_IMPORT_RE.test(rootClaude)) {
|
|
490
|
+
return { ok: true, via: "root-import" };
|
|
491
|
+
}
|
|
492
|
+
const dotClaude = await readText(path.join(root, ".claude", "CLAUDE.md"));
|
|
493
|
+
if (dotClaude !== null && DOT_CLAUDE_IMPORT_RE.test(dotClaude)) {
|
|
494
|
+
return { ok: true, via: "dotclaude-import" };
|
|
495
|
+
}
|
|
496
|
+
if (rootClaude !== null || dotClaude !== null) {
|
|
497
|
+
return {
|
|
498
|
+
ok: false,
|
|
499
|
+
kind: "no-import",
|
|
500
|
+
detail: rootClaude !== null ? "AGENTS.md and CLAUDE.md both present, but CLAUDE.md does not import it" : "AGENTS.md and .claude/CLAUDE.md both present, but .claude/CLAUDE.md does not import it"
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
const version = claudeVersion === void 0 ? await detectClaudeCodeVersion() : claudeVersion;
|
|
504
|
+
if (version !== null && versionBefore(version, CLAUDE_AGENTS_FALLBACK_VERSION)) {
|
|
505
|
+
return {
|
|
506
|
+
ok: false,
|
|
507
|
+
kind: "old-reader",
|
|
508
|
+
detail: `AGENTS.md present, no CLAUDE.md, and Claude Code ${version} predates the AGENTS.md fallback (${CLAUDE_AGENTS_FALLBACK_VERSION})`
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
return { ok: true, via: "native-fallback" };
|
|
512
|
+
}
|
|
441
513
|
async function walkTree(root) {
|
|
442
514
|
const state = { budget: 2e4, truncated: false };
|
|
443
515
|
const countFiles = async (dir) => {
|
|
@@ -558,11 +630,12 @@ async function scanProject(root, opts = {}) {
|
|
|
558
630
|
const [branch, head, hooksPathRaw, authorsRaw, subjectsRaw] = isRepo ? await Promise.all([
|
|
559
631
|
git(abs, ["rev-parse", "--abbrev-ref", "HEAD"]),
|
|
560
632
|
git(abs, ["rev-parse", "--short", "HEAD"]),
|
|
561
|
-
git
|
|
633
|
+
// `--type=path` expands a leading `~` the way git itself does when it runs the hooks.
|
|
634
|
+
git(abs, ["config", "--type=path", "--get", "core.hooksPath"]),
|
|
562
635
|
git(abs, ["log", "-200", "--format=%ae"]),
|
|
563
636
|
git(abs, ["log", "-50", "--format=%s"])
|
|
564
637
|
]) : [null, null, null, null, null];
|
|
565
|
-
const hooksPath = hooksPathRaw
|
|
638
|
+
const hooksPath = hooksPathRaw ? normalizeHooksPath(abs, hooksPathRaw) : void 0;
|
|
566
639
|
const hooks = await detectHooks(abs, hooksPath, rootPkg);
|
|
567
640
|
const shell = await detectShellSurface(abs, isRepo);
|
|
568
641
|
const freshness = await collectFreshness(abs, isRepo, artifacts, opts.upstreamRemote);
|
|
@@ -602,4 +675,4 @@ async function scanProject(root, opts = {}) {
|
|
|
602
675
|
};
|
|
603
676
|
}
|
|
604
677
|
|
|
605
|
-
export { expandFileGlobs, scanProject };
|
|
678
|
+
export { CLAUDE_AGENTS_FALLBACK_VERSION, checkClaudePointer, expandFileGlobs, scanProject };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { expandFileGlobs } from './chunk-
|
|
2
|
+
import { expandFileGlobs } from './chunk-DLHC7QON.js';
|
|
3
3
|
import { DEFAULT_CONFIG, CONFIG_FILE } from './chunk-P6ATKV2R.js';
|
|
4
4
|
import { readText, isDirectory, matchesAnyGlob, normalizeRelPath, readJson, pathExists, git } from './chunk-4VPBP6K6.js';
|
|
5
5
|
import path3 from 'path';
|
|
@@ -439,10 +439,13 @@ function extractCommandClaims(text) {
|
|
|
439
439
|
return { scripts, filteredSkipped };
|
|
440
440
|
}
|
|
441
441
|
var PATH_TOKEN_RE = /^[A-Za-z0-9_.-]+(\/[A-Za-z0-9_.$-]+)+\/?$/;
|
|
442
|
+
var HOSTNAME_RE = /^[a-z0-9-]+(?:\.[a-z0-9-]+)*\.([a-z]{2,})$/i;
|
|
442
443
|
var KNOWN_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
443
444
|
..."ts tsx cts mts js jsx cjs mjs json jsonc json5 md mdx mdc yml yaml toml ini cfg conf env sh bash zsh fish ps1 bat cmd css scss sass less html htm xml svg sql prisma graphql gql proto py rb rs go java kt kts swift c h cc cpp hpp cs php vue svelte astro txt log lock csv tsv png jpg jpeg gif webp ico avif woff woff2 ttf otf wasm map pem key crt tf tfvars example sample local snap ejs hbs pug".split(" ")
|
|
444
445
|
]);
|
|
445
446
|
var CREATION_CONTEXT_RE = /\b(?:creat(?:e|es|ed|ing)|generat(?:e|es|ed|ing)|scaffold(?:s|ed|ing)?|quarantin(?:e|es|ed|ing)|(?:writ(?:e|es|ten|ing)|output(?:s|ted)?|emit(?:s|ted|ting)?|sav(?:e|es|ed|ing)|mov(?:e|es|ed|ing)|copy|copi(?:es|ed))\s+(?:it\s+|them\s+)?(?:to|into)|new\s+(?:file|directory|folder)|will\s+(?:be\s+)?(?:created|generated|written)|add(?:s|ed|ing)?\s+(?:a|the)\s+new)\b/i;
|
|
447
|
+
var FETCH_CONTEXT_RE = /\b(?:fetch(?:es|ed|ing)?|pull(?:s|ed|ing)?|clone(?:s|d)?|download(?:s|ed|ing)?)\b/i;
|
|
448
|
+
var EXTERNAL_URL_RE = /https?:\/\/|\bwww\./i;
|
|
446
449
|
var PLACEHOLDER_SEGMENTS = /* @__PURE__ */ new Set(["placeholder", "foo", "bar", "baz", "qux"]);
|
|
447
450
|
var PLACEHOLDER_PREFIX_RE = /^(?:my|your)-/i;
|
|
448
451
|
function isPlaceholderClaim(token) {
|
|
@@ -476,14 +479,21 @@ ${line}`;
|
|
|
476
479
|
}
|
|
477
480
|
function extractPathClaims(text, opts = {}) {
|
|
478
481
|
const prospectiveOnly = /* @__PURE__ */ new Map();
|
|
482
|
+
const fetchedOnly = /* @__PURE__ */ new Map();
|
|
479
483
|
const namespacedOnly = /* @__PURE__ */ new Map();
|
|
480
484
|
const placeholder = /* @__PURE__ */ new Set();
|
|
485
|
+
const dirs = /* @__PURE__ */ new Set();
|
|
481
486
|
for (const m of text.matchAll(/`([^`\n]+)`/g)) {
|
|
482
487
|
const token = m[1].trim();
|
|
483
488
|
if (token.includes(" ") || token.length > 120) continue;
|
|
484
489
|
if (token.startsWith("/") || token.startsWith("~") || token.startsWith("@") || token.startsWith("$"))
|
|
485
490
|
continue;
|
|
486
491
|
if (token.includes("://") || token.startsWith("www.")) continue;
|
|
492
|
+
if (token.includes("/")) {
|
|
493
|
+
const firstSegment = token.split("/")[0] ?? token;
|
|
494
|
+
const hostSuffix = HOSTNAME_RE.exec(firstSegment)?.[1]?.toLowerCase();
|
|
495
|
+
if (hostSuffix && !KNOWN_EXTENSIONS.has(hostSuffix)) continue;
|
|
496
|
+
}
|
|
487
497
|
if (/[*?{}<>|]/.test(token)) continue;
|
|
488
498
|
if (token.includes("@")) continue;
|
|
489
499
|
if (!PATH_TOKEN_RE.test(token)) continue;
|
|
@@ -496,8 +506,12 @@ function extractPathClaims(text, opts = {}) {
|
|
|
496
506
|
placeholder.add(claim);
|
|
497
507
|
continue;
|
|
498
508
|
}
|
|
499
|
-
|
|
509
|
+
if (isDirClaim) dirs.add(claim);
|
|
510
|
+
const context = claimContext(text, m.index ?? 0);
|
|
511
|
+
const prospective2 = CREATION_CONTEXT_RE.test(context);
|
|
500
512
|
prospectiveOnly.set(claim, (prospectiveOnly.get(claim) ?? true) && prospective2);
|
|
513
|
+
const fetched2 = FETCH_CONTEXT_RE.test(context) && EXTERNAL_URL_RE.test(context);
|
|
514
|
+
fetchedOnly.set(claim, (fetchedOnly.get(claim) ?? true) && fetched2);
|
|
501
515
|
if (opts.namespaces) {
|
|
502
516
|
const nsLead = new RegExp(`(${NAMESPACE_IDENT}):[ \\t]*$`).exec(text.slice(0, m.index ?? 0));
|
|
503
517
|
const prefixed = isNamespace(nsLead?.[1]);
|
|
@@ -506,13 +520,15 @@ function extractPathClaims(text, opts = {}) {
|
|
|
506
520
|
}
|
|
507
521
|
const paths = [];
|
|
508
522
|
const prospective = [];
|
|
523
|
+
const fetched = [];
|
|
509
524
|
const namespaced = [];
|
|
510
525
|
for (const [claim, only] of prospectiveOnly) {
|
|
511
|
-
if (
|
|
526
|
+
if (fetchedOnly.get(claim)) fetched.push(claim);
|
|
527
|
+
else if (only) prospective.push(claim);
|
|
512
528
|
else if (namespacedOnly.get(claim)) namespaced.push(claim);
|
|
513
529
|
else paths.push(claim);
|
|
514
530
|
}
|
|
515
|
-
return { paths, prospective, namespaced, placeholder: [...placeholder] };
|
|
531
|
+
return { paths, dirs: [...dirs], prospective, fetched, namespaced, placeholder: [...placeholder] };
|
|
516
532
|
}
|
|
517
533
|
var LOCAL_REF_LEADINS = new Set(
|
|
518
534
|
"decision decisions entry entries ruling rulings record records ledger id ids item items see per in of on at by to as is was are were the a an and or but not with under over from via vs than after before since between through against latest newest earliest only also still now supersedes superseded superseding amends amended extends extended cites cited citing adds added adding wrote written writes locked locks closed closes opened opens resolves resolved reopened recorded number numbers".split(" ")
|
|
@@ -582,12 +598,14 @@ async function buildTruthEnv(root, facts) {
|
|
|
582
598
|
for (const key of Object.keys(pkgJson?.scripts ?? {})) knownScripts.add(key);
|
|
583
599
|
}
|
|
584
600
|
const bases = [root, ...facts.packages.map((p) => path3.join(root, p.dir))];
|
|
585
|
-
const pathResolves = async (claim) => {
|
|
601
|
+
const pathResolves = async (claim, fromDir) => {
|
|
586
602
|
for (const base of bases) {
|
|
587
603
|
if (await pathExists(path3.join(base, claim))) return true;
|
|
588
604
|
if (await pathExists(path3.join(base, "src", claim))) return true;
|
|
589
605
|
if (await pathExists(path3.join(base, "scripts", claim))) return true;
|
|
590
606
|
}
|
|
607
|
+
if (fromDir && fromDir !== "." && await pathExists(path3.join(root, fromDir, claim)))
|
|
608
|
+
return true;
|
|
591
609
|
return false;
|
|
592
610
|
};
|
|
593
611
|
const binResolves = async (name) => {
|
|
@@ -617,6 +635,7 @@ function emptyCounters() {
|
|
|
617
635
|
unverifiableCommands: 0,
|
|
618
636
|
gitignoredSkipped: 0,
|
|
619
637
|
prospectiveSkipped: 0,
|
|
638
|
+
fetchedSkipped: 0,
|
|
620
639
|
placeholderSkipped: 0,
|
|
621
640
|
qualifiedRefsSkipped: 0,
|
|
622
641
|
unresolvableRefs: 0,
|
|
@@ -659,19 +678,48 @@ async function checkTextClaims(env, file, opts, counters) {
|
|
|
659
678
|
confidence: "high"
|
|
660
679
|
});
|
|
661
680
|
}
|
|
662
|
-
const { paths, prospective, placeholder, namespaced } = extractPathClaims(
|
|
663
|
-
|
|
664
|
-
|
|
681
|
+
const { paths, dirs, prospective, fetched, placeholder, namespaced } = extractPathClaims(
|
|
682
|
+
file.text,
|
|
683
|
+
{ namespaces: opts.treatNamespacedPrefixes }
|
|
684
|
+
);
|
|
665
685
|
counters.prospectiveSkipped += prospective.length;
|
|
686
|
+
counters.fetchedSkipped += fetched.length;
|
|
666
687
|
counters.placeholderSkipped += placeholder.length;
|
|
667
688
|
counters.namespacedSkipped += namespaced.length;
|
|
668
|
-
const
|
|
689
|
+
const fromDir = path3.dirname(file.path);
|
|
690
|
+
const unresolved = [];
|
|
669
691
|
for (const claim of paths) {
|
|
670
|
-
if (await env.pathResolves(claim
|
|
692
|
+
if (await env.pathResolves(claim, fromDir))
|
|
693
|
+
examined.push({ kind: "path", value: claim, exists: true });
|
|
694
|
+
else unresolved.push(claim);
|
|
695
|
+
}
|
|
696
|
+
const proseBases = /* @__PURE__ */ new Set();
|
|
697
|
+
for (const dirClaim of dirs) {
|
|
698
|
+
if (await env.pathResolves(dirClaim, fromDir)) {
|
|
699
|
+
proseBases.add(dirClaim);
|
|
700
|
+
proseBases.add(path3.dirname(dirClaim));
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
const missing = [];
|
|
704
|
+
for (const claim of unresolved) {
|
|
705
|
+
let anchored = false;
|
|
706
|
+
for (const base of proseBases) {
|
|
707
|
+
if (await pathExists(path3.join(env.root, base, claim))) anchored = true;
|
|
708
|
+
else if (fromDir !== "." && await pathExists(path3.join(env.root, fromDir, base, claim)))
|
|
709
|
+
anchored = true;
|
|
710
|
+
if (anchored) break;
|
|
711
|
+
}
|
|
712
|
+
if (anchored) examined.push({ kind: "path", value: claim, exists: true });
|
|
671
713
|
else missing.push(claim);
|
|
672
714
|
}
|
|
673
715
|
const ignoredOut = missing.length ? await git(env.root, ["check-ignore", ...missing]) : null;
|
|
674
716
|
const gitignored = new Set((ignoredOut ?? "").split("\n").filter(Boolean));
|
|
717
|
+
const unproven = missing.filter((claim) => !gitignored.has(claim));
|
|
718
|
+
if (unproven.length) {
|
|
719
|
+
const dirOut = await git(env.root, ["check-ignore", ...unproven.map((claim) => `${claim}/`)]);
|
|
720
|
+
for (const line of (dirOut ?? "").split("\n").filter(Boolean))
|
|
721
|
+
gitignored.add(line.replace(/\/$/, ""));
|
|
722
|
+
}
|
|
675
723
|
let pathFindings = 0;
|
|
676
724
|
for (const claim of missing) {
|
|
677
725
|
if (opts.rootedFirstSegments && !opts.rootedFirstSegments.has(claim.split("/")[0] ?? claim)) {
|
|
@@ -780,4 +828,4 @@ function checkDecisionRefs(file, ledger, opts, counters) {
|
|
|
780
828
|
return { findings, examined };
|
|
781
829
|
}
|
|
782
830
|
|
|
783
|
-
export { KNOWN_EXTENSIONS, NAMESPACE_IDENT, NAMESPACE_STOP, PATH_TOKEN_RE, buildTruthEnv, checkDecisionRefs, checkDocRefs, checkTextClaims, emptyCounters, listInstructionFiles, listStateDocuments, loadDecisionLedger, meetsFailOn, packageManagerUsage, parseFailOnTier, rankFindings, stateFreshnessLens };
|
|
831
|
+
export { HOSTNAME_RE, KNOWN_EXTENSIONS, NAMESPACE_IDENT, NAMESPACE_STOP, PATH_TOKEN_RE, buildTruthEnv, checkDecisionRefs, checkDocRefs, checkTextClaims, emptyCounters, listInstructionFiles, listStateDocuments, loadDecisionLedger, meetsFailOn, packageManagerUsage, parseFailOnTier, rankFindings, stateFreshnessLens };
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { isSafeGateCommand, generateAgentsMd, generateClaudePointerMd, isSelfBuildRepo, generatePreCommitHook, generateCommitMsgHook, generatePrePushHook, generateShellDiscoveryScript, generateArtifactCheckScript, fileOrigin } from './chunk-HSRHOVII.js';
|
|
3
|
+
import { DEFAULT_CONFIG } from './chunk-P6ATKV2R.js';
|
|
4
|
+
import { readText, pathExists } from './chunk-4VPBP6K6.js';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
|
|
7
|
+
function derivedCommands(facts, existingHook) {
|
|
8
|
+
const c = facts.commands;
|
|
9
|
+
const base = [c.formatCheck, c.typecheck, c.lint].filter(
|
|
10
|
+
(k) => Boolean(k) && isSafeGateCommand(c.raw[k])
|
|
11
|
+
);
|
|
12
|
+
if (c.test && existingHook && new RegExp(`\\b${c.test}\\b`).test(existingHook)) {
|
|
13
|
+
base.push(c.test);
|
|
14
|
+
}
|
|
15
|
+
return base;
|
|
16
|
+
}
|
|
17
|
+
function riskReachability(facts) {
|
|
18
|
+
const reasons = [];
|
|
19
|
+
if (facts.publishRoute !== "none") {
|
|
20
|
+
reasons.push("an instruction file can name a package script that no longer exists");
|
|
21
|
+
}
|
|
22
|
+
if (facts.artifacts.some((a) => a.kind === "state" && a.exists)) {
|
|
23
|
+
reasons.push("a state doc can fall far enough behind the repo to escalate");
|
|
24
|
+
}
|
|
25
|
+
return reasons;
|
|
26
|
+
}
|
|
27
|
+
function deriveFailOn(facts, recorded) {
|
|
28
|
+
const reachable = riskReachability(facts);
|
|
29
|
+
if (recorded.explicit) return { failOn: recorded.failOn, source: "config", reachable };
|
|
30
|
+
if (recorded.failOn === "risk" && reachable.length === 0) {
|
|
31
|
+
return { failOn: "gap", source: "derived", reachable };
|
|
32
|
+
}
|
|
33
|
+
return { failOn: recorded.failOn, source: "default", reachable };
|
|
34
|
+
}
|
|
35
|
+
async function planWorkflow(root, facts, opts) {
|
|
36
|
+
const out = [];
|
|
37
|
+
const add = async (rel, contents, label, executable = false) => {
|
|
38
|
+
const abs = path.join(root, rel);
|
|
39
|
+
const exists = await pathExists(abs);
|
|
40
|
+
const existing = exists ? await readText(abs) : null;
|
|
41
|
+
const differs = exists ? existing !== contents : void 0;
|
|
42
|
+
const origin = differs && existing !== null ? fileOrigin(existing) : void 0;
|
|
43
|
+
out.push({
|
|
44
|
+
path: rel,
|
|
45
|
+
contents,
|
|
46
|
+
exists,
|
|
47
|
+
differs,
|
|
48
|
+
drift: origin === "pack" ? "stale" : origin,
|
|
49
|
+
executable,
|
|
50
|
+
label
|
|
51
|
+
});
|
|
52
|
+
};
|
|
53
|
+
if (opts.agents) {
|
|
54
|
+
await add("AGENTS.md", generateAgentsMd(facts), "Minimal operating contract (scaffold)");
|
|
55
|
+
await add("CLAUDE.md", generateClaudePointerMd(), "Claude Code pointer to AGENTS.md");
|
|
56
|
+
}
|
|
57
|
+
if (opts.gates) {
|
|
58
|
+
const existingPrePush = await readText(path.join(root, ".githooks", "pre-push"));
|
|
59
|
+
const selfBuild = isSelfBuildRepo(facts);
|
|
60
|
+
await add(
|
|
61
|
+
".githooks/pre-commit",
|
|
62
|
+
generatePreCommitHook(selfBuild),
|
|
63
|
+
"Process gate (pre-commit)",
|
|
64
|
+
true
|
|
65
|
+
);
|
|
66
|
+
await add(
|
|
67
|
+
".githooks/commit-msg",
|
|
68
|
+
generateCommitMsgHook(opts.gateConfig),
|
|
69
|
+
"Message gate (content screen, format)",
|
|
70
|
+
true
|
|
71
|
+
);
|
|
72
|
+
const gateConfig = opts.gateConfig?.commands?.length ? opts.gateConfig : {
|
|
73
|
+
commands: derivedCommands(facts, existingPrePush ?? void 0),
|
|
74
|
+
failOn: opts.gateConfig?.failOn ?? DEFAULT_CONFIG.gates.failOn,
|
|
75
|
+
publishGate: opts.gateConfig?.publishGate,
|
|
76
|
+
commitFormat: opts.gateConfig?.commitFormat,
|
|
77
|
+
allowWriting: opts.gateConfig?.allowWriting ?? []
|
|
78
|
+
};
|
|
79
|
+
const tier = deriveFailOn(facts, {
|
|
80
|
+
failOn: gateConfig.failOn,
|
|
81
|
+
explicit: opts.gateFailOnPinned ?? false
|
|
82
|
+
});
|
|
83
|
+
await add(
|
|
84
|
+
".githooks/pre-push",
|
|
85
|
+
generatePrePushHook(facts, { ...gateConfig, failOn: tier.failOn }, selfBuild),
|
|
86
|
+
"Correctness gate (pre-push)",
|
|
87
|
+
true
|
|
88
|
+
);
|
|
89
|
+
if (facts.shell?.scripts) {
|
|
90
|
+
await add(
|
|
91
|
+
".githooks/discover-shell-scripts.sh",
|
|
92
|
+
generateShellDiscoveryScript(),
|
|
93
|
+
"Shell discovery (pre-push shellcheck helper)",
|
|
94
|
+
true
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
if (opts.gateConfig?.publishGate ?? opts.publishGate ?? facts.publishable) {
|
|
98
|
+
await add(
|
|
99
|
+
"scripts/artifact-check.sh",
|
|
100
|
+
generateArtifactCheckScript(selfBuild),
|
|
101
|
+
"Content screen (published artifact)",
|
|
102
|
+
true
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return out;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export { deriveFailOn, derivedCommands, planWorkflow, riskReachability };
|