@monte3l/groundwork 1.0.0-rc.2 → 1.0.0-rc.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -5
- package/bin/m3l-groundwork.mjs +36 -2
- package/dist/assets.d.ts +10 -0
- package/dist/assets.js +14 -0
- package/dist/baseline-stage.d.ts +173 -0
- package/dist/baseline-stage.js +215 -0
- package/dist/caps.d.ts +4 -1
- package/dist/caps.js +17 -3
- package/dist/conflicts.d.ts +23 -2
- package/dist/conflicts.js +89 -11
- package/dist/customize-paths.d.ts +92 -0
- package/dist/customize-paths.js +115 -0
- package/dist/emit.d.ts +50 -1
- package/dist/emit.js +121 -13
- package/dist/fatal.d.ts +70 -0
- package/dist/fatal.js +132 -0
- package/dist/format-error.d.ts +113 -0
- package/dist/format-error.js +553 -0
- package/dist/fs-guard.d.ts +142 -0
- package/dist/fs-guard.js +222 -0
- package/dist/git.js +10 -1
- package/dist/harness/conformance.js +2 -0
- package/dist/harness/frontmatter.js +2 -13
- package/dist/harness/grade.js +37 -8
- package/dist/harness/rules.d.ts +20 -3
- package/dist/harness/rules.js +108 -6
- package/dist/harness/types.d.ts +13 -0
- package/dist/harness/types.js +3 -0
- package/dist/inventory.d.ts +77 -3
- package/dist/inventory.js +103 -12
- package/dist/jsonc.d.ts +49 -2
- package/dist/jsonc.js +140 -7
- package/dist/main.d.ts +62 -3
- package/dist/main.js +538 -67
- package/dist/merge-json.d.ts +47 -4
- package/dist/merge-json.js +120 -8
- package/dist/mode.js +12 -2
- package/dist/pack-stage.d.ts +210 -0
- package/dist/pack-stage.js +287 -0
- package/dist/packs.d.ts +19 -13
- package/dist/packs.js +231 -28
- package/dist/palette.d.ts +23 -0
- package/dist/palette.js +22 -0
- package/dist/plugin.d.ts +122 -6
- package/dist/plugin.js +687 -47
- package/dist/report.d.ts +21 -2
- package/dist/report.js +93 -5
- package/dist/staging.d.ts +176 -0
- package/dist/staging.js +375 -0
- package/dist/survey/fs-walk.d.ts +34 -2
- package/dist/survey/fs-walk.js +70 -5
- package/dist/survey/internal/blocked-path.d.ts +27 -0
- package/dist/survey/internal/blocked-path.js +129 -0
- package/dist/survey/internal/package-json.d.ts +13 -0
- package/dist/survey/internal/package-json.js +37 -0
- package/dist/survey/internal/read-guard.d.ts +178 -0
- package/dist/survey/internal/read-guard.js +276 -0
- package/dist/survey/survey-docs.d.ts +17 -2
- package/dist/survey/survey-docs.js +61 -21
- package/dist/survey/survey-harness.d.ts +20 -2
- package/dist/survey/survey-harness.js +87 -46
- package/dist/survey/survey-shape.d.ts +17 -2
- package/dist/survey/survey-shape.js +60 -46
- package/dist/survey/survey-toolchain.d.ts +16 -1
- package/dist/survey/survey-toolchain.js +69 -66
- package/dist/survey/survey.js +6 -4
- package/dist/survey/types.d.ts +79 -0
- package/dist/survey/types.js +2 -6
- package/dist/term.d.ts +80 -0
- package/dist/term.js +145 -0
- package/dist/tokens.js +2 -0
- package/dist/toolchain/conformance.js +2 -0
- package/dist/toolchain/grade.js +26 -8
- package/dist/toolchain/rules.d.ts +13 -4
- package/dist/toolchain/rules.js +16 -0
- package/dist/toolchain/tsconfig-chain.d.ts +2 -0
- package/dist/toolchain/tsconfig-chain.js +32 -8
- package/dist/toolchain/types.d.ts +16 -4
- package/dist/toolchain/types.js +3 -0
- package/package.json +4 -3
- package/plugin/skills/customize/SKILL.md +292 -18
- package/plugin/src/domain-map.ts +39 -14
- package/plugin/src/index.ts +5 -1
- package/plugin/src/kind-facet-map.ts +25 -8
- package/plugin/src/pack-map.ts +147 -25
- package/plugin/src/plugin-map.ts +236 -0
- package/templates/core/.claude/agents/Explore.md +0 -1
- package/templates/core/.claude/agents/code-implementer.md +4 -4
- package/templates/core/.claude/agents/code-reviewer.md +4 -4
- package/templates/core/.claude/agents/silent-failure-hunter.md +2 -2
- package/templates/core/.claude/agents/test-author.md +7 -5
- package/templates/core/.claude/hooks/guard-branch-isolation.mjs +56 -10
- package/templates/core/.claude/hooks/guard-double-background.mjs +13 -8
- package/templates/core/.claude/hooks/guard-git-push-signed.mjs +12 -9
- package/templates/core/.claude/hooks/guard-hub-src-writes.mjs +1890 -38
- package/templates/core/.claude/hooks/guard-js-extension.mjs +2 -1
- package/templates/core/.claude/hooks/guard-no-commonjs.mjs +7 -5
- package/templates/core/.claude/hooks/guard-secret-writes.mjs +7 -5
- package/templates/core/.claude/hooks/inject-decision-gate.mjs +12 -9
- package/templates/core/.claude/hooks/post-edit-verify.mjs +276 -98
- package/templates/core/.claude/rules/agent-dispatch.md +7 -0
- package/templates/core/.claude/rules/tests.md +2 -2
- package/templates/core/.claude/settings.json +5 -0
- package/templates/core/.claude/skills/finishing-work/SKILL.md +58 -10
- package/templates/core/.claude/skills/harness-guidance/SKILL.md +16 -7
- package/templates/core/.claude/skills/starting-work/SKILL.md +5 -0
- package/templates/core/.claude/skills/triaging-ci/SKILL.md +9 -8
- package/templates/core/.claude/skills/typescript-guidance/SKILL.md +137 -20
- package/templates/core/.claude/skills/typescript-guidance/references/area-catalog.md +135 -0
- package/templates/core/.claude/skills/typescript-guidance/references/tooling-sources.md +113 -0
- package/templates/core/.claude/skills/writing-commits/SKILL.md +2 -2
- package/templates/core/.github/dependabot.yml +18 -0
- package/templates/core/.github/workflows/ci.yml +15 -15
- package/templates/core/.github/workflows/dependency-review.yml +2 -2
- package/templates/core/.github/workflows/security-audit.yml +10 -4
- package/templates/core/.prettierignore +4 -0
- package/templates/core/CLAUDE.md +53 -3
- package/templates/core/README.md +30 -10
- package/templates/core/_gitignore +17 -0
- package/templates/core/bin/check-exports.mjs +11 -5
- package/templates/core/bin/lib/agent-roster.mjs +1 -1
- package/templates/core/bin/lib/frontmatter.mjs +4 -2
- package/templates/core/bin/lib/harness-rules.mjs +169 -20
- package/templates/core/bin/lib/protected-paths.mjs +93 -5
- package/templates/core/bin/lib/toolchain-rules.mjs +187 -26
- package/templates/core/bin/lib/verify-steps.mjs +29 -11
- package/templates/core/bin/verify.mjs +12 -0
- package/templates/core/eslint.config.js +5 -0
- package/templates/core/package.json +7 -7
- package/templates/core/vitest.config.ts +8 -1
- package/templates/packs/README.md +35 -24
- package/templates/packs/github/files/.claude/skills/reviewing-dependabot-prs/SKILL.md +133 -0
- package/templates/packs/github/files/.claude/skills/triaging-scan-alerts/SKILL.md +88 -0
- package/templates/packs/github/files/.claude/skills/watching-pr-checks/SKILL.md +94 -0
- package/templates/packs/github/files/.github/workflows/claude-pr-review.yml +267 -0
- package/templates/packs/github/files/.github/workflows/claude.yml +106 -0
- package/templates/packs/github/pack.json +19 -0
- package/templates/packs/harness-extras/files/.claude/hooks/guard-readonly-bash.mjs +1122 -65
- package/templates/packs/harness-extras/files/.claude/hooks/reinject-compact-handoff.mjs +147 -13
- package/templates/packs/{statusline → harness-extras}/files/.claude/hooks/statusline.mjs +6 -2
- package/templates/packs/harness-extras/files/.claude/hooks/write-compact-handoff.mjs +227 -44
- package/templates/packs/harness-extras/pack.json +18 -13
- package/templates/packs/publishing/files/.changeset/README.md +25 -0
- package/templates/packs/publishing/files/.changeset/config.json +7 -0
- package/templates/packs/publishing/files/.github/release-tools/package.json +9 -0
- package/templates/packs/publishing/files/.github/workflows/release.yml +293 -0
- package/templates/packs/publishing/files/REUSE.toml +28 -0
- package/templates/packs/publishing/files/bin/check-dts-deps.mjs +234 -0
- package/templates/packs/publishing/files/bin/check-license-headers.mjs +217 -0
- package/templates/packs/publishing/files/bin/check-publish-version.mjs +149 -0
- package/templates/packs/publishing/files/bin/lib/npm-publish-args.mjs +86 -0
- package/templates/packs/publishing/files/bin/pnpm-publish-shim.mjs +86 -0
- package/templates/packs/publishing/pack.json +35 -0
- package/templates/packs/{harness-extras → quality}/files/bin/check-file-budget.mjs +6 -4
- package/templates/packs/quality/pack.json +29 -0
- package/templates/packs/supply-chain/files/.github/workflows/gitleaks.yml +52 -0
- package/templates/packs/supply-chain/files/.github/workflows/scorecard.yml +48 -0
- package/templates/packs/supply-chain/files/.gitleaks.toml +2 -0
- package/templates/packs/supply-chain/pack.json +19 -0
- package/templates/packs/worktrees/files/.claude/hooks/ensure-worktree-deps.mjs +283 -0
- package/templates/packs/worktrees/files/.claude/hooks/guard-worktree-only.mjs +245 -0
- package/templates/packs/worktrees/files/.claude/hooks/repair-core-bare.mjs +335 -0
- package/templates/packs/worktrees/files/.claude/skills/working-in-worktrees/SKILL.md +139 -0
- package/templates/packs/worktrees/files/.worktreeinclude +11 -0
- package/templates/packs/worktrees/pack.json +64 -0
- package/templates/packs/statusline/pack.json +0 -31
- /package/templates/packs/{statusline → harness-extras}/files/.claude/hooks/statusline-layout.mjs +0 -0
- /package/templates/packs/{statusline → harness-extras}/files/.claude/hooks/subagent-statusline.mjs +0 -0
- /package/templates/packs/{harness-extras → quality}/files/.claude/agents/type-design-analyzer.md +0 -0
- /package/templates/packs/{harness-extras → quality}/files/bin/file-budget-baseline.json +0 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reads `dir/package.json` as an object. Absent is `undefined` with nothing
|
|
3
|
+
* recorded; unreadable (`EACCES`/`EPERM`) or unparseable is `undefined` with
|
|
4
|
+
* an `undetermined` entry; any other read failure throws.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```ts
|
|
8
|
+
* const undetermined: string[] = [];
|
|
9
|
+
* const manifest = readPackageJson("/path/to/project", undetermined);
|
|
10
|
+
* ```
|
|
11
|
+
*/
|
|
12
|
+
export declare function readPackageJson(dir: string, undetermined: string[]): Record<string, unknown> | undefined;
|
|
13
|
+
//# sourceMappingURL=package-json.d.ts.map
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: Copyright the m3l-groundwork contributors
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
/**
|
|
4
|
+
* The `package.json` read `survey-shape.ts` and `survey-toolchain.ts` share,
|
|
5
|
+
* so the two collectors cannot disagree about what an unreadable or
|
|
6
|
+
* malformed manifest means.
|
|
7
|
+
*/
|
|
8
|
+
import { readFileSync } from "node:fs";
|
|
9
|
+
import { join } from "node:path";
|
|
10
|
+
import { guardedExists, guardedRead } from "./read-guard.js";
|
|
11
|
+
/**
|
|
12
|
+
* Reads `dir/package.json` as an object. Absent is `undefined` with nothing
|
|
13
|
+
* recorded; unreadable (`EACCES`/`EPERM`) or unparseable is `undefined` with
|
|
14
|
+
* an `undetermined` entry; any other read failure throws.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```ts
|
|
18
|
+
* const undetermined: string[] = [];
|
|
19
|
+
* const manifest = readPackageJson("/path/to/project", undetermined);
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export function readPackageJson(dir, undetermined) {
|
|
23
|
+
const path = join(dir, "package.json");
|
|
24
|
+
if (!guardedExists(path, undetermined))
|
|
25
|
+
return undefined;
|
|
26
|
+
const content = guardedRead(path, () => readFileSync(path, "utf8"), undetermined);
|
|
27
|
+
if (content === undefined)
|
|
28
|
+
return undefined;
|
|
29
|
+
try {
|
|
30
|
+
return JSON.parse(content);
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
undetermined.push(`could not parse ${path}: ${error instanceof Error ? error.message : String(error)}`);
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=package-json.js.map
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A survey read that failed for a reason other than permission. Carries the
|
|
3
|
+
* original failure as `cause`.
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* ```ts
|
|
7
|
+
* throw new SurveyReadError("could not read /p/README.md", { cause });
|
|
8
|
+
* ```
|
|
9
|
+
*/
|
|
10
|
+
export declare class SurveyReadError extends Error {
|
|
11
|
+
name: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* The `code` of a Node system error, or `undefined` for anything without a
|
|
15
|
+
* string `code`.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```ts
|
|
19
|
+
* errnoCode(Object.assign(new Error("x"), { code: "ENOENT" })); // "ENOENT"
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export declare function errnoCode(error: unknown): string | undefined;
|
|
23
|
+
/**
|
|
24
|
+
* The permission errno carried by `error` (`EACCES`/`EPERM`), or `undefined`
|
|
25
|
+
* when `error` is anything else.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```ts
|
|
29
|
+
* permissionCode(Object.assign(new Error("x"), { code: "EACCES" })); // "EACCES"
|
|
30
|
+
* permissionCode(Object.assign(new Error("x"), { code: "EIO" })); // undefined
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export declare function permissionCode(error: unknown): string | undefined;
|
|
34
|
+
/**
|
|
35
|
+
* The `undetermined` entry recorded for an unreadable path. Identical text
|
|
36
|
+
* for a file and a directory, so two collectors that hit the same
|
|
37
|
+
* unreadable path produce one entry once the aggregate de-duplicates.
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```ts
|
|
41
|
+
* unreadableNote("/p/CLAUDE.md", "EACCES");
|
|
42
|
+
* // "/p/CLAUDE.md is unreadable (EACCES) -- its contents are not in this survey"
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
export declare function unreadableNote(path: string, code: string): string;
|
|
46
|
+
/**
|
|
47
|
+
* Wraps a non-permission read failure on `path` in a {@link SurveyReadError}
|
|
48
|
+
* naming the path, with `cause` as the original failure (which carries the
|
|
49
|
+
* errno code itself).
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* ```ts
|
|
53
|
+
* try {
|
|
54
|
+
* readFileSync(path, "utf8");
|
|
55
|
+
* } catch (cause) {
|
|
56
|
+
* throw readFailure(path, cause);
|
|
57
|
+
* }
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
60
|
+
export declare function readFailure(path: string, cause: unknown): SurveyReadError;
|
|
61
|
+
/**
|
|
62
|
+
* Whether `error` carries an errno that means "nothing is at this path"
|
|
63
|
+
* (`ENOENT`/`ENOTDIR`).
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* ```ts
|
|
67
|
+
* isAbsentError(Object.assign(new Error("x"), { code: "ENOENT" })); // true
|
|
68
|
+
* isAbsentError(Object.assign(new Error("x"), { code: "ELOOP" })); // false
|
|
69
|
+
* ```
|
|
70
|
+
*/
|
|
71
|
+
export declare function isAbsentError(error: unknown): boolean;
|
|
72
|
+
/**
|
|
73
|
+
* The errno to record when `error` says the path holds something this
|
|
74
|
+
* process cannot read or resolve -- a permission code (`EACCES`/`EPERM`) or
|
|
75
|
+
* a symlink loop (`ELOOP`) -- or `undefined` for anything else.
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* ```ts
|
|
79
|
+
* unresolvableCode(Object.assign(new Error("x"), { code: "ELOOP" })); // "ELOOP"
|
|
80
|
+
* unresolvableCode(Object.assign(new Error("x"), { code: "EIO" })); // undefined
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
83
|
+
export declare function unresolvableCode(error: unknown): string | undefined;
|
|
84
|
+
/**
|
|
85
|
+
* What a `stat` on a path established: something is there, nothing is there,
|
|
86
|
+
* or something is there (or may be) that this process cannot reach --
|
|
87
|
+
* carrying the errno that says why.
|
|
88
|
+
*/
|
|
89
|
+
export type PathProbe = {
|
|
90
|
+
readonly kind: "present";
|
|
91
|
+
} | {
|
|
92
|
+
readonly kind: "absent";
|
|
93
|
+
} | {
|
|
94
|
+
readonly kind: "unresolvable";
|
|
95
|
+
readonly code: string;
|
|
96
|
+
};
|
|
97
|
+
/**
|
|
98
|
+
* Probes `path` with a real `stat`, never `existsSync` (which answers
|
|
99
|
+
* `false` for ANY failure, so a file under a `chmod 000` directory reads as
|
|
100
|
+
* absent). `ENOENT`/`ENOTDIR` is `absent`; `EACCES`/`EPERM`/`ELOOP` is
|
|
101
|
+
* `unresolvable` with the errno -- never folded into `absent`. Any other
|
|
102
|
+
* failure throws a {@link SurveyReadError} naming `path`, with the original
|
|
103
|
+
* as `cause`.
|
|
104
|
+
*
|
|
105
|
+
* @example
|
|
106
|
+
* ```ts
|
|
107
|
+
* const probe = probePath(targetPath);
|
|
108
|
+
* if (probe.kind === "unresolvable") undetermined.push(unreadableNote(targetPath, probe.code));
|
|
109
|
+
* ```
|
|
110
|
+
*/
|
|
111
|
+
export declare function probePath(path: string): PathProbe;
|
|
112
|
+
/**
|
|
113
|
+
* The path an `undetermined` note names when a `stat` of `path` failed with
|
|
114
|
+
* `code` (from {@link probePath}'s `unresolvable` result). A `stat` needs
|
|
115
|
+
* search permission on the path's ancestors, never on the path itself, so a
|
|
116
|
+
* permission failure names the enclosing directory -- one entry for every
|
|
117
|
+
* probe under it once the caller de-duplicates. A symlink loop (`ELOOP`) is
|
|
118
|
+
* a property of the path itself, so it names `path`.
|
|
119
|
+
*
|
|
120
|
+
* @example
|
|
121
|
+
* ```ts
|
|
122
|
+
* const probe = probePath(targetPath);
|
|
123
|
+
* if (probe.kind === "unresolvable") {
|
|
124
|
+
* undetermined.push(unreadableNote(probeSubject(targetPath, probe.code), probe.code));
|
|
125
|
+
* }
|
|
126
|
+
* ```
|
|
127
|
+
*/
|
|
128
|
+
export declare function probeSubject(path: string, code: string): string;
|
|
129
|
+
/**
|
|
130
|
+
* Whether `path` exists, without `existsSync`'s blind spot: `existsSync`
|
|
131
|
+
* answers `false` for ANY `stat` failure, so a path under a directory this
|
|
132
|
+
* process may not enter (`chmod 000`) reads as silently absent. A `stat`
|
|
133
|
+
* needs search permission on the path's ancestors, never on the path
|
|
134
|
+
* itself, so a permission failure here is recorded in `undetermined` naming
|
|
135
|
+
* the enclosing directory (one entry for every probe under it once the
|
|
136
|
+
* aggregate de-duplicates) and answers `false`; the survey carries on. A
|
|
137
|
+
* symlink loop (`ELOOP`) is a property of the path itself, so it is
|
|
138
|
+
* recorded naming `path` and answers `false`. A genuinely absent path
|
|
139
|
+
* (`ENOENT`/`ENOTDIR`) answers `false` with nothing recorded. Any other
|
|
140
|
+
* failure throws a {@link SurveyReadError} naming `path`, with the original
|
|
141
|
+
* as `cause`.
|
|
142
|
+
*
|
|
143
|
+
* @example
|
|
144
|
+
* ```ts
|
|
145
|
+
* if (!guardedExists(settingsPath, undetermined)) return undefined;
|
|
146
|
+
* ```
|
|
147
|
+
*/
|
|
148
|
+
export declare function guardedExists(path: string, undetermined: string[]): boolean;
|
|
149
|
+
/**
|
|
150
|
+
* The errno to record when a read of an already-discovered path failed for a
|
|
151
|
+
* reason that is a property of the project's own tree -- `EACCES`/`EPERM`,
|
|
152
|
+
* `ENOENT` (a dangling symlink), `ELOOP`, `EISDIR` or `ENOTDIR` -- or `undefined` for
|
|
153
|
+
* anything else (`EIO`, `EMFILE`, ...), which the caller throws.
|
|
154
|
+
*
|
|
155
|
+
* @example
|
|
156
|
+
* ```ts
|
|
157
|
+
* recordedReadCode(Object.assign(new Error("x"), { code: "EISDIR" })); // "EISDIR"
|
|
158
|
+
* recordedReadCode(Object.assign(new Error("x"), { code: "EIO" })); // undefined
|
|
159
|
+
* ```
|
|
160
|
+
*/
|
|
161
|
+
export declare function recordedReadCode(error: unknown): string | undefined;
|
|
162
|
+
/**
|
|
163
|
+
* Runs `read` against `path`. On a failure that is a property of the
|
|
164
|
+
* project's own tree -- a permission failure (`EACCES`/`EPERM`), a dangling
|
|
165
|
+
* symlink (`ENOENT`), a symlink loop (`ELOOP`), a directory where a file
|
|
166
|
+
* was expected (`EISDIR`), or a file where a directory was expected
|
|
167
|
+
* (`ENOTDIR`) -- records the path and errno in `undetermined`
|
|
168
|
+
* and returns `undefined`. On any other failure (`EIO`, `EMFILE`, ...),
|
|
169
|
+
* throws a {@link SurveyReadError} chaining the original as `cause`.
|
|
170
|
+
*
|
|
171
|
+
* @example
|
|
172
|
+
* ```ts
|
|
173
|
+
* const content = guardedRead(path, () => readFileSync(path, "utf8"), undetermined);
|
|
174
|
+
* const headings = content === undefined ? [] : extractHeadings(content);
|
|
175
|
+
* ```
|
|
176
|
+
*/
|
|
177
|
+
export declare function guardedRead<T>(path: string, read: () => T, undetermined: string[]): T | undefined;
|
|
178
|
+
//# sourceMappingURL=read-guard.d.ts.map
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: Copyright the m3l-groundwork contributors
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
/**
|
|
4
|
+
* The one errno discrimination every adopt-mode survey read goes through.
|
|
5
|
+
* A permission failure (`EACCES`/`EPERM`) is a property of the project file
|
|
6
|
+
* itself -- a `chmod 000` left behind, a root-owned file -- so it is recorded
|
|
7
|
+
* in the survey's `undetermined` list and the survey carries on; so is a
|
|
8
|
+
* dangling symlink (`ENOENT`), a symlink loop (`ELOOP`), a directory where
|
|
9
|
+
* a file was expected (`EISDIR`) or a file where a directory was expected
|
|
10
|
+
* (`ENOTDIR`) met on a read. Anything
|
|
11
|
+
* else (`EIO`, `EMFILE`, ...) says something about the machine, not the
|
|
12
|
+
* project, and is thrown with the path named and the original chained as
|
|
13
|
+
* `cause`, never folded silently in beside a genuine permission problem.
|
|
14
|
+
*/
|
|
15
|
+
import { statSync } from "node:fs";
|
|
16
|
+
import { dirname } from "node:path";
|
|
17
|
+
/** The errno codes that mean "this entry exists but this process may not read it". */
|
|
18
|
+
const PERMISSION_CODES = new Set(["EACCES", "EPERM"]);
|
|
19
|
+
/**
|
|
20
|
+
* A survey read that failed for a reason other than permission. Carries the
|
|
21
|
+
* original failure as `cause`.
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```ts
|
|
25
|
+
* throw new SurveyReadError("could not read /p/README.md", { cause });
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export class SurveyReadError extends Error {
|
|
29
|
+
name = "SurveyReadError";
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* The `code` of a Node system error, or `undefined` for anything without a
|
|
33
|
+
* string `code`.
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```ts
|
|
37
|
+
* errnoCode(Object.assign(new Error("x"), { code: "ENOENT" })); // "ENOENT"
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
export function errnoCode(error) {
|
|
41
|
+
if (typeof error !== "object" || error === null)
|
|
42
|
+
return undefined;
|
|
43
|
+
// One read into a local, so the value checked is the value returned.
|
|
44
|
+
const code = Reflect.get(error, "code");
|
|
45
|
+
return typeof code === "string" ? code : undefined;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* The permission errno carried by `error` (`EACCES`/`EPERM`), or `undefined`
|
|
49
|
+
* when `error` is anything else.
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* ```ts
|
|
53
|
+
* permissionCode(Object.assign(new Error("x"), { code: "EACCES" })); // "EACCES"
|
|
54
|
+
* permissionCode(Object.assign(new Error("x"), { code: "EIO" })); // undefined
|
|
55
|
+
* ```
|
|
56
|
+
*/
|
|
57
|
+
export function permissionCode(error) {
|
|
58
|
+
const code = errnoCode(error);
|
|
59
|
+
return code !== undefined && PERMISSION_CODES.has(code) ? code : undefined;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* The `undetermined` entry recorded for an unreadable path. Identical text
|
|
63
|
+
* for a file and a directory, so two collectors that hit the same
|
|
64
|
+
* unreadable path produce one entry once the aggregate de-duplicates.
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* ```ts
|
|
68
|
+
* unreadableNote("/p/CLAUDE.md", "EACCES");
|
|
69
|
+
* // "/p/CLAUDE.md is unreadable (EACCES) -- its contents are not in this survey"
|
|
70
|
+
* ```
|
|
71
|
+
*/
|
|
72
|
+
export function unreadableNote(path, code) {
|
|
73
|
+
return `${path} is unreadable (${code}) -- its contents are not in this survey`;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Wraps a non-permission read failure on `path` in a {@link SurveyReadError}
|
|
77
|
+
* naming the path, with `cause` as the original failure (which carries the
|
|
78
|
+
* errno code itself).
|
|
79
|
+
*
|
|
80
|
+
* @example
|
|
81
|
+
* ```ts
|
|
82
|
+
* try {
|
|
83
|
+
* readFileSync(path, "utf8");
|
|
84
|
+
* } catch (cause) {
|
|
85
|
+
* throw readFailure(path, cause);
|
|
86
|
+
* }
|
|
87
|
+
* ```
|
|
88
|
+
*/
|
|
89
|
+
export function readFailure(path, cause) {
|
|
90
|
+
return new SurveyReadError(`could not read ${path}`, { cause });
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* The errno codes that mean "nothing is at this path": nothing there
|
|
94
|
+
* (`ENOENT`) or an ancestor is a file (`ENOTDIR`). The one definition every
|
|
95
|
+
* survey probe (`guardedExists`, `fs-walk.ts`'s `walkBounded`) and
|
|
96
|
+
* `jsonc.ts`'s `readJsoncFile` share, so they cannot disagree on what
|
|
97
|
+
* "absent" means. `ELOOP` is deliberately NOT here: a symlink loop is
|
|
98
|
+
* something at the path this process cannot resolve, not an absence -- see
|
|
99
|
+
* {@link UNRESOLVABLE_CODE}.
|
|
100
|
+
*/
|
|
101
|
+
const ABSENT_CODES = new Set(["ENOENT", "ENOTDIR"]);
|
|
102
|
+
/**
|
|
103
|
+
* The errno a symlink loop raises: an entry exists at the path, but it never
|
|
104
|
+
* resolves. Recorded like a permission failure, never folded into "absent".
|
|
105
|
+
*/
|
|
106
|
+
const UNRESOLVABLE_CODE = "ELOOP";
|
|
107
|
+
/**
|
|
108
|
+
* Whether `error` carries an errno that means "nothing is at this path"
|
|
109
|
+
* (`ENOENT`/`ENOTDIR`).
|
|
110
|
+
*
|
|
111
|
+
* @example
|
|
112
|
+
* ```ts
|
|
113
|
+
* isAbsentError(Object.assign(new Error("x"), { code: "ENOENT" })); // true
|
|
114
|
+
* isAbsentError(Object.assign(new Error("x"), { code: "ELOOP" })); // false
|
|
115
|
+
* ```
|
|
116
|
+
*/
|
|
117
|
+
export function isAbsentError(error) {
|
|
118
|
+
const code = errnoCode(error);
|
|
119
|
+
return code !== undefined && ABSENT_CODES.has(code);
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* The errno to record when `error` says the path holds something this
|
|
123
|
+
* process cannot read or resolve -- a permission code (`EACCES`/`EPERM`) or
|
|
124
|
+
* a symlink loop (`ELOOP`) -- or `undefined` for anything else.
|
|
125
|
+
*
|
|
126
|
+
* @example
|
|
127
|
+
* ```ts
|
|
128
|
+
* unresolvableCode(Object.assign(new Error("x"), { code: "ELOOP" })); // "ELOOP"
|
|
129
|
+
* unresolvableCode(Object.assign(new Error("x"), { code: "EIO" })); // undefined
|
|
130
|
+
* ```
|
|
131
|
+
*/
|
|
132
|
+
export function unresolvableCode(error) {
|
|
133
|
+
const permission = permissionCode(error);
|
|
134
|
+
if (permission !== undefined)
|
|
135
|
+
return permission;
|
|
136
|
+
return errnoCode(error) === UNRESOLVABLE_CODE ? UNRESOLVABLE_CODE : undefined;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Probes `path` with a real `stat`, never `existsSync` (which answers
|
|
140
|
+
* `false` for ANY failure, so a file under a `chmod 000` directory reads as
|
|
141
|
+
* absent). `ENOENT`/`ENOTDIR` is `absent`; `EACCES`/`EPERM`/`ELOOP` is
|
|
142
|
+
* `unresolvable` with the errno -- never folded into `absent`. Any other
|
|
143
|
+
* failure throws a {@link SurveyReadError} naming `path`, with the original
|
|
144
|
+
* as `cause`.
|
|
145
|
+
*
|
|
146
|
+
* @example
|
|
147
|
+
* ```ts
|
|
148
|
+
* const probe = probePath(targetPath);
|
|
149
|
+
* if (probe.kind === "unresolvable") undetermined.push(unreadableNote(targetPath, probe.code));
|
|
150
|
+
* ```
|
|
151
|
+
*/
|
|
152
|
+
export function probePath(path) {
|
|
153
|
+
try {
|
|
154
|
+
statSync(path);
|
|
155
|
+
return { kind: "present" };
|
|
156
|
+
}
|
|
157
|
+
catch (error) {
|
|
158
|
+
if (isAbsentError(error))
|
|
159
|
+
return { kind: "absent" };
|
|
160
|
+
const code = unresolvableCode(error);
|
|
161
|
+
if (code !== undefined)
|
|
162
|
+
return { kind: "unresolvable", code };
|
|
163
|
+
throw new SurveyReadError(`could not check whether ${path} exists`, {
|
|
164
|
+
cause: error,
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* The path an `undetermined` note names when a `stat` of `path` failed with
|
|
170
|
+
* `code` (from {@link probePath}'s `unresolvable` result). A `stat` needs
|
|
171
|
+
* search permission on the path's ancestors, never on the path itself, so a
|
|
172
|
+
* permission failure names the enclosing directory -- one entry for every
|
|
173
|
+
* probe under it once the caller de-duplicates. A symlink loop (`ELOOP`) is
|
|
174
|
+
* a property of the path itself, so it names `path`.
|
|
175
|
+
*
|
|
176
|
+
* @example
|
|
177
|
+
* ```ts
|
|
178
|
+
* const probe = probePath(targetPath);
|
|
179
|
+
* if (probe.kind === "unresolvable") {
|
|
180
|
+
* undetermined.push(unreadableNote(probeSubject(targetPath, probe.code), probe.code));
|
|
181
|
+
* }
|
|
182
|
+
* ```
|
|
183
|
+
*/
|
|
184
|
+
export function probeSubject(path, code) {
|
|
185
|
+
return code === UNRESOLVABLE_CODE ? path : dirname(path);
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Whether `path` exists, without `existsSync`'s blind spot: `existsSync`
|
|
189
|
+
* answers `false` for ANY `stat` failure, so a path under a directory this
|
|
190
|
+
* process may not enter (`chmod 000`) reads as silently absent. A `stat`
|
|
191
|
+
* needs search permission on the path's ancestors, never on the path
|
|
192
|
+
* itself, so a permission failure here is recorded in `undetermined` naming
|
|
193
|
+
* the enclosing directory (one entry for every probe under it once the
|
|
194
|
+
* aggregate de-duplicates) and answers `false`; the survey carries on. A
|
|
195
|
+
* symlink loop (`ELOOP`) is a property of the path itself, so it is
|
|
196
|
+
* recorded naming `path` and answers `false`. A genuinely absent path
|
|
197
|
+
* (`ENOENT`/`ENOTDIR`) answers `false` with nothing recorded. Any other
|
|
198
|
+
* failure throws a {@link SurveyReadError} naming `path`, with the original
|
|
199
|
+
* as `cause`.
|
|
200
|
+
*
|
|
201
|
+
* @example
|
|
202
|
+
* ```ts
|
|
203
|
+
* if (!guardedExists(settingsPath, undetermined)) return undefined;
|
|
204
|
+
* ```
|
|
205
|
+
*/
|
|
206
|
+
export function guardedExists(path, undetermined) {
|
|
207
|
+
const probe = probePath(path);
|
|
208
|
+
if (probe.kind === "present")
|
|
209
|
+
return true;
|
|
210
|
+
if (probe.kind === "absent")
|
|
211
|
+
return false;
|
|
212
|
+
undetermined.push(unreadableNote(probeSubject(path, probe.code), probe.code));
|
|
213
|
+
return false;
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* The non-permission errnos a read of an already-discovered path records
|
|
217
|
+
* rather than throws: the entry is a dangling symlink (`ENOENT`), a symlink
|
|
218
|
+
* loop (`ELOOP`), a directory where a file was expected (`EISDIR`), or a
|
|
219
|
+
* file where a directory was expected (`ENOTDIR`, e.g. a `readdirSync` of a
|
|
220
|
+
* path a prior `stat` found present). Each
|
|
221
|
+
* is a property of the project's own tree, like a permission failure -- not
|
|
222
|
+
* of the machine, like `EIO`/`EMFILE`.
|
|
223
|
+
*/
|
|
224
|
+
const RECORDED_READ_CODES = new Set([
|
|
225
|
+
"ENOENT",
|
|
226
|
+
UNRESOLVABLE_CODE,
|
|
227
|
+
"EISDIR",
|
|
228
|
+
"ENOTDIR",
|
|
229
|
+
]);
|
|
230
|
+
/**
|
|
231
|
+
* The errno to record when a read of an already-discovered path failed for a
|
|
232
|
+
* reason that is a property of the project's own tree -- `EACCES`/`EPERM`,
|
|
233
|
+
* `ENOENT` (a dangling symlink), `ELOOP`, `EISDIR` or `ENOTDIR` -- or `undefined` for
|
|
234
|
+
* anything else (`EIO`, `EMFILE`, ...), which the caller throws.
|
|
235
|
+
*
|
|
236
|
+
* @example
|
|
237
|
+
* ```ts
|
|
238
|
+
* recordedReadCode(Object.assign(new Error("x"), { code: "EISDIR" })); // "EISDIR"
|
|
239
|
+
* recordedReadCode(Object.assign(new Error("x"), { code: "EIO" })); // undefined
|
|
240
|
+
* ```
|
|
241
|
+
*/
|
|
242
|
+
export function recordedReadCode(error) {
|
|
243
|
+
const permission = permissionCode(error);
|
|
244
|
+
if (permission !== undefined)
|
|
245
|
+
return permission;
|
|
246
|
+
const code = errnoCode(error);
|
|
247
|
+
return code !== undefined && RECORDED_READ_CODES.has(code) ? code : undefined;
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Runs `read` against `path`. On a failure that is a property of the
|
|
251
|
+
* project's own tree -- a permission failure (`EACCES`/`EPERM`), a dangling
|
|
252
|
+
* symlink (`ENOENT`), a symlink loop (`ELOOP`), a directory where a file
|
|
253
|
+
* was expected (`EISDIR`), or a file where a directory was expected
|
|
254
|
+
* (`ENOTDIR`) -- records the path and errno in `undetermined`
|
|
255
|
+
* and returns `undefined`. On any other failure (`EIO`, `EMFILE`, ...),
|
|
256
|
+
* throws a {@link SurveyReadError} chaining the original as `cause`.
|
|
257
|
+
*
|
|
258
|
+
* @example
|
|
259
|
+
* ```ts
|
|
260
|
+
* const content = guardedRead(path, () => readFileSync(path, "utf8"), undetermined);
|
|
261
|
+
* const headings = content === undefined ? [] : extractHeadings(content);
|
|
262
|
+
* ```
|
|
263
|
+
*/
|
|
264
|
+
export function guardedRead(path, read, undetermined) {
|
|
265
|
+
try {
|
|
266
|
+
return read();
|
|
267
|
+
}
|
|
268
|
+
catch (error) {
|
|
269
|
+
const code = recordedReadCode(error);
|
|
270
|
+
if (code === undefined)
|
|
271
|
+
throw readFailure(path, error);
|
|
272
|
+
undetermined.push(unreadableNote(path, code));
|
|
273
|
+
return undefined;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
//# sourceMappingURL=read-guard.js.map
|
|
@@ -1,4 +1,19 @@
|
|
|
1
1
|
import type { DocsSurvey } from "./types.js";
|
|
2
|
-
/**
|
|
3
|
-
|
|
2
|
+
/**
|
|
3
|
+
* Indexes docs/guideline files at `dir`. Offline, read-only, index-only --
|
|
4
|
+
* no full content. A doc or doc directory that exists but cannot be read
|
|
5
|
+
* (`EACCES`/`EPERM`) is recorded in `undetermined` (an unreadable doc keeps
|
|
6
|
+
* its entry with empty `headings`); any other read failure throws, naming
|
|
7
|
+
* the path, with the original failure as `cause`.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* import { surveyDocs } from "./survey-docs.js";
|
|
12
|
+
*
|
|
13
|
+
* const undetermined: string[] = [];
|
|
14
|
+
* const docs = surveyDocs("/path/to/project", undetermined);
|
|
15
|
+
* console.log(docs.files.map((file) => file.path), undetermined);
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
export declare function surveyDocs(dir: string, undetermined: string[]): DocsSurvey;
|
|
4
19
|
//# sourceMappingURL=survey-docs.d.ts.map
|
|
@@ -1,14 +1,18 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: Copyright the m3l-groundwork contributors
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
1
3
|
/**
|
|
2
4
|
* Indexes human-facing docs and guidelines: `CONTRIBUTING.md`, a
|
|
3
5
|
* `docs/contributing/` tree, ADR/decision/RFC directories, style guides, and
|
|
4
6
|
* the README's own heading outline. This is an index (path, size, headings)
|
|
5
|
-
* so `/customize`'s Step 0
|
|
7
|
+
* so `/customize`'s Step 0 (the adopt-mode reconcile step in `/customize`)
|
|
8
|
+
* knows what exists and where to read it in full --
|
|
6
9
|
* it never inlines a doc's content itself, which would make the survey's
|
|
7
10
|
* own output as large as the docs it's indexing.
|
|
8
11
|
*/
|
|
9
|
-
import {
|
|
12
|
+
import { readFileSync, statSync } from "node:fs";
|
|
10
13
|
import { join } from "node:path";
|
|
11
14
|
import { walkBounded } from "./fs-walk.js";
|
|
15
|
+
import { guardedExists, guardedRead } from "./internal/read-guard.js";
|
|
12
16
|
const NAMED_ROOT_CANDIDATES = ["README.md", "CONTRIBUTING.md"];
|
|
13
17
|
const NAMED_DIR_CANDIDATES = [
|
|
14
18
|
"docs/contributing",
|
|
@@ -24,46 +28,82 @@ function extractHeadings(content) {
|
|
|
24
28
|
.filter((line) => /^#{1,3}\s/.test(line))
|
|
25
29
|
.map((line) => line.replace(/^#{1,3}\s*/, "").trim());
|
|
26
30
|
}
|
|
27
|
-
|
|
28
|
-
|
|
31
|
+
/**
|
|
32
|
+
* Indexes one doc. Its size comes from `statSync`, which needs no read
|
|
33
|
+
* permission on the file itself, so an unreadable doc keeps its entry with
|
|
34
|
+
* empty `headings` (and is recorded); a doc that cannot even be stat'ed is
|
|
35
|
+
* left out (and recorded).
|
|
36
|
+
*/
|
|
37
|
+
function indexFile(path, undetermined) {
|
|
38
|
+
const sizeBytes = guardedRead(path, () => statSync(path).size, undetermined);
|
|
39
|
+
if (sizeBytes === undefined)
|
|
40
|
+
return undefined;
|
|
41
|
+
const content = guardedRead(path, () => readFileSync(path, "utf8"), undetermined);
|
|
29
42
|
return {
|
|
30
43
|
path,
|
|
31
|
-
sizeBytes
|
|
32
|
-
headings: extractHeadings(content),
|
|
44
|
+
sizeBytes,
|
|
45
|
+
headings: content === undefined ? [] : extractHeadings(content),
|
|
33
46
|
};
|
|
34
47
|
}
|
|
35
|
-
function collectRootMarkdown(dir) {
|
|
36
|
-
const
|
|
48
|
+
function collectRootMarkdown(dir, undetermined) {
|
|
49
|
+
const paths = [];
|
|
37
50
|
for (const name of NAMED_ROOT_CANDIDATES) {
|
|
38
51
|
const path = join(dir, name);
|
|
39
|
-
if (
|
|
40
|
-
|
|
52
|
+
if (guardedExists(path, undetermined))
|
|
53
|
+
paths.push(path);
|
|
41
54
|
}
|
|
42
|
-
for (const entry of walkBounded(dir, 0)) {
|
|
55
|
+
for (const entry of walkBounded(dir, 0, undetermined)) {
|
|
43
56
|
if (!entry.isDirectory && /^STYLE.*\.md$/i.test(entry.relPath)) {
|
|
44
|
-
|
|
57
|
+
paths.push(entry.path);
|
|
45
58
|
}
|
|
46
59
|
}
|
|
47
|
-
return
|
|
60
|
+
return indexAll(paths, undetermined);
|
|
48
61
|
}
|
|
49
|
-
function collectNamedDirectories(dir) {
|
|
50
|
-
const
|
|
62
|
+
function collectNamedDirectories(dir, undetermined) {
|
|
63
|
+
const paths = [];
|
|
51
64
|
for (const relDir of NAMED_DIR_CANDIDATES) {
|
|
52
65
|
const absDir = join(dir, relDir);
|
|
53
|
-
if (!
|
|
66
|
+
if (!guardedExists(absDir, undetermined))
|
|
54
67
|
continue;
|
|
55
|
-
for (const entry of walkBounded(absDir, 2)) {
|
|
68
|
+
for (const entry of walkBounded(absDir, 2, undetermined)) {
|
|
56
69
|
if (!entry.isDirectory && entry.relPath.endsWith(".md")) {
|
|
57
|
-
|
|
70
|
+
paths.push(entry.path);
|
|
58
71
|
}
|
|
59
72
|
}
|
|
60
73
|
}
|
|
74
|
+
return indexAll(paths, undetermined);
|
|
75
|
+
}
|
|
76
|
+
function indexAll(paths, undetermined) {
|
|
77
|
+
const found = [];
|
|
78
|
+
for (const path of paths) {
|
|
79
|
+
const file = indexFile(path, undetermined);
|
|
80
|
+
if (file !== undefined)
|
|
81
|
+
found.push(file);
|
|
82
|
+
}
|
|
61
83
|
return found;
|
|
62
84
|
}
|
|
63
|
-
/**
|
|
64
|
-
|
|
85
|
+
/**
|
|
86
|
+
* Indexes docs/guideline files at `dir`. Offline, read-only, index-only --
|
|
87
|
+
* no full content. A doc or doc directory that exists but cannot be read
|
|
88
|
+
* (`EACCES`/`EPERM`) is recorded in `undetermined` (an unreadable doc keeps
|
|
89
|
+
* its entry with empty `headings`); any other read failure throws, naming
|
|
90
|
+
* the path, with the original failure as `cause`.
|
|
91
|
+
*
|
|
92
|
+
* @example
|
|
93
|
+
* ```ts
|
|
94
|
+
* import { surveyDocs } from "./survey-docs.js";
|
|
95
|
+
*
|
|
96
|
+
* const undetermined: string[] = [];
|
|
97
|
+
* const docs = surveyDocs("/path/to/project", undetermined);
|
|
98
|
+
* console.log(docs.files.map((file) => file.path), undetermined);
|
|
99
|
+
* ```
|
|
100
|
+
*/
|
|
101
|
+
export function surveyDocs(dir, undetermined) {
|
|
65
102
|
return {
|
|
66
|
-
files: [
|
|
103
|
+
files: [
|
|
104
|
+
...collectRootMarkdown(dir, undetermined),
|
|
105
|
+
...collectNamedDirectories(dir, undetermined),
|
|
106
|
+
],
|
|
67
107
|
};
|
|
68
108
|
}
|
|
69
109
|
//# sourceMappingURL=survey-docs.js.map
|
|
@@ -1,4 +1,22 @@
|
|
|
1
1
|
import type { HarnessSurvey } from "./types.js";
|
|
2
|
-
/**
|
|
3
|
-
|
|
2
|
+
/**
|
|
3
|
+
* Surveys the `.claude/` harness and `CLAUDE.md` at `dir`. Offline,
|
|
4
|
+
* read-only. A file or directory that exists but cannot be read
|
|
5
|
+
* (`EACCES`/`EPERM`) is left out of its collection and recorded in
|
|
6
|
+
* `undetermined` -- an unreadable `CLAUDE.md` still counts as present, with
|
|
7
|
+
* no headings. A `.claude/` this process may not enter is still `present`,
|
|
8
|
+
* with every collection empty and the directory recorded in `undetermined`
|
|
9
|
+
* -- never reported as an empty harness. Any other read failure throws, naming the path, with the
|
|
10
|
+
* original failure as `cause`.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* import { surveyHarness } from "./survey-harness.js";
|
|
15
|
+
*
|
|
16
|
+
* const undetermined: string[] = [];
|
|
17
|
+
* const harness = surveyHarness("/path/to/project", undetermined);
|
|
18
|
+
* console.log(harness.agents.map((agent) => agent.name), undetermined);
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
export declare function surveyHarness(dir: string, undetermined: string[]): HarnessSurvey;
|
|
4
22
|
//# sourceMappingURL=survey-harness.d.ts.map
|