autonomous-sdlc-harness 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/NOTICE +7 -0
- package/README.md +24 -0
- package/dist/cli.js +194 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/config.js +561 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/daemon.js +791 -0
- package/dist/commands/daemon.js.map +1 -0
- package/dist/commands/doctor.js +336 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/init.js +2023 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/registry.js +42 -0
- package/dist/commands/registry.js.map +1 -0
- package/dist/config/check.js +505 -0
- package/dist/config/check.js.map +1 -0
- package/dist/config/io.js +177 -0
- package/dist/config/io.js.map +1 -0
- package/dist/config/model.js +406 -0
- package/dist/config/model.js.map +1 -0
- package/dist/core/errors.js +71 -0
- package/dist/core/errors.js.map +1 -0
- package/dist/core/git.js +537 -0
- package/dist/core/git.js.map +1 -0
- package/dist/core/json.js +125 -0
- package/dist/core/json.js.map +1 -0
- package/dist/core/layerCoverage.js +141 -0
- package/dist/core/layerCoverage.js.map +1 -0
- package/dist/core/layerGapRemedy.js +62 -0
- package/dist/core/layerGapRemedy.js.map +1 -0
- package/dist/core/nameList.js +23 -0
- package/dist/core/nameList.js.map +1 -0
- package/dist/core/paths.js +153 -0
- package/dist/core/paths.js.map +1 -0
- package/dist/core/prompt.js +206 -0
- package/dist/core/prompt.js.map +1 -0
- package/dist/core/repoPaths.js +55 -0
- package/dist/core/repoPaths.js.map +1 -0
- package/dist/core/report.js +150 -0
- package/dist/core/report.js.map +1 -0
- package/dist/core/templating.js +88 -0
- package/dist/core/templating.js.map +1 -0
- package/dist/core/writer.js +479 -0
- package/dist/core/writer.js.map +1 -0
- package/dist/daemon/backend.js +180 -0
- package/dist/daemon/backend.js.map +1 -0
- package/dist/daemon/units.js +380 -0
- package/dist/daemon/units.js.map +1 -0
- package/dist/detect/nestedApplication.js +79 -0
- package/dist/detect/nestedApplication.js.map +1 -0
- package/dist/detect/presets.js +2033 -0
- package/dist/detect/presets.js.map +1 -0
- package/dist/detect/signals.js +1368 -0
- package/dist/detect/signals.js.map +1 -0
- package/dist/doctor/checks.js +3530 -0
- package/dist/doctor/checks.js.map +1 -0
- package/dist/generators/claudeContext.js +588 -0
- package/dist/generators/claudeContext.js.map +1 -0
- package/dist/generators/githooks.js +446 -0
- package/dist/generators/githooks.js.map +1 -0
- package/dist/generators/harnessConfig.js +632 -0
- package/dist/generators/harnessConfig.js.map +1 -0
- package/dist/generators/notifications.js +191 -0
- package/dist/generators/notifications.js.map +1 -0
- package/dist/generators/outerLoopScripts.js +165 -0
- package/dist/generators/outerLoopScripts.js.map +1 -0
- package/dist/generators/permissionProfile.js +1172 -0
- package/dist/generators/permissionProfile.js.map +1 -0
- package/dist/generators/projectSettings.js +322 -0
- package/dist/generators/projectSettings.js.map +1 -0
- package/dist/generators/repoRoot.js +417 -0
- package/dist/generators/repoRoot.js.map +1 -0
- package/dist/generators/scripts.js +557 -0
- package/dist/generators/scripts.js.map +1 -0
- package/dist/generators/stateDir.js +221 -0
- package/dist/generators/stateDir.js.map +1 -0
- package/dist/machine/paths.js +111 -0
- package/dist/machine/paths.js.map +1 -0
- package/dist/machine/plugins.js +224 -0
- package/dist/machine/plugins.js.map +1 -0
- package/dist/machine/registry.js +330 -0
- package/dist/machine/registry.js.map +1 -0
- package/package.json +23 -0
- package/scripts/README.md +13 -0
- package/scripts/daemon/launchd.plist.template +59 -0
- package/scripts/daemon/systemd.service.template +58 -0
- package/templates/README.md +15 -0
- package/templates/claude/CLAUDE.md +54 -0
- package/templates/claude/README.md +5 -0
- package/templates/claude/context/api.md +29 -0
- package/templates/claude/context/conventions.md +23 -0
- package/templates/claude/context/data-layer.md +28 -0
- package/templates/claude/context/data-storage.md +29 -0
- package/templates/claude/context/docs-catalog.md +29 -0
- package/templates/claude/context/domain.md +28 -0
- package/templates/claude/context/layer.md +20 -0
- package/templates/claude/context/module.md +30 -0
- package/templates/claude/context/package.md +29 -0
- package/templates/claude/context/presentation.md +32 -0
- package/templates/claude/context/state-slices.md +28 -0
- package/templates/claude/context/tests.md +28 -0
- package/templates/claude/harness-task-offer.md +58 -0
- package/templates/claude/push-notify.env.example +21 -0
- package/templates/claude/qa-accounts.env.example +38 -0
- package/templates/claude/qa_test_scenarios.md +110 -0
- package/templates/claude/settings.autonomous.json +93 -0
- package/templates/claude/settings.autonomous.qa.json +36 -0
- package/templates/githooks/README.md +3 -0
- package/templates/githooks/pre-push +72 -0
- package/templates/repo/README.md +3 -0
- package/templates/repo/gitattributes +16 -0
- package/templates/repo/gitignore +61 -0
- package/templates/repo/gitignore.qa +25 -0
- package/templates/repo/mcp.json +17 -0
- package/templates/scripts/README.md +5 -0
- package/templates/scripts/autonomous-format-stream.sh +95 -0
- package/templates/scripts/autonomous-notify.sh +337 -0
- package/templates/scripts/autonomous-watcher.sh +3087 -0
- package/templates/scripts/cleanup-merged-worktrees.sh +327 -0
- package/templates/scripts/commit-on-branch.sh +288 -0
- package/templates/scripts/create-worktree.sh +360 -0
- package/templates/scripts/deploy.sh +47 -0
- package/templates/scripts/lib/harness-run-lib.sh +1481 -0
- package/templates/scripts/push-branch.sh +140 -0
- package/templates/scripts/refresh-branch.sh +244 -0
- package/templates/scripts/restart-watcher.sh +401 -0
- package/templates/scripts/scratch-run.sh +302 -0
- package/templates/scripts/setup-worktree.sh +262 -0
- package/templates/scripts/start-dev-server.sh +99 -0
- package/templates/scripts/test.sh +50 -0
- package/templates/scripts/typecheck.sh +50 -0
- package/templates/state-dir/README-root.md +13 -0
- package/templates/state-dir/README.md +9 -0
- package/templates/state-dir/architecture_branch_review_point_reviews/README.md +9 -0
- package/templates/state-dir/architecture_branch_reviews/README.md +9 -0
- package/templates/state-dir/architecture_reviews/README.md +9 -0
- package/templates/state-dir/architecture_user_review_reviews/README.md +9 -0
- package/templates/state-dir/autonomous_inbox/README.md +9 -0
- package/templates/state-dir/autonomous_logs/README.md +9 -0
- package/templates/state-dir/branch_statistics/README.md +9 -0
- package/templates/state-dir/business_parity_branch_review_point_reviews/README.md +9 -0
- package/templates/state-dir/business_parity_branch_reviews/README.md +9 -0
- package/templates/state-dir/business_parity_reviews/README.md +9 -0
- package/templates/state-dir/business_parity_user_review_reviews/README.md +9 -0
- package/templates/state-dir/clarification_digests/README.md +9 -0
- package/templates/state-dir/clarifications/README.md +9 -0
- package/templates/state-dir/code_reviews/README.md +9 -0
- package/templates/state-dir/dispatch_additions/README.md +19 -0
- package/templates/state-dir/docs_catalog/README.md +9 -0
- package/templates/state-dir/flow_progress/README.md +9 -0
- package/templates/state-dir/improvement_observations/README.md +19 -0
- package/templates/state-dir/improvement_suggestions.md +29 -0
- package/templates/state-dir/lessons.md +23 -0
- package/templates/state-dir/qa_review_point_reviews/README.md +9 -0
- package/templates/state-dir/qa_reviews/README.md +9 -0
- package/templates/state-dir/review_plan_point_reviews/README.md +9 -0
- package/templates/state-dir/review_plan_reviews/README.md +9 -0
- package/templates/state-dir/scratch/README.md +11 -0
- package/templates/state-dir/skeptic_review_plan_reviews/README.md +9 -0
- package/templates/state-dir/skeptic_review_point_reviews/README.md +9 -0
- package/templates/state-dir/skeptic_reviews/README.md +9 -0
- package/templates/state-dir/story_plans/README.md +9 -0
- package/templates/state-dir/task_plan_point_reviews/README.md +9 -0
- package/templates/state-dir/task_plan_reviews/README.md +9 -0
- package/templates/state-dir/task_plans/README.md +9 -0
- package/templates/state-dir/task_prompts/README.md +9 -0
- package/templates/state-dir/ui_test_plan_reviews/README.md +9 -0
- package/templates/state-dir/ui_test_plans/README.md +9 -0
- package/templates/state-dir/user_review_fix_plan_point_reviews/README.md +9 -0
- package/templates/state-dir/user_reviews/README.md +9 -0
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The CLI's error and exit-code contract.
|
|
3
|
+
*
|
|
4
|
+
* Two rules hold across the whole package:
|
|
5
|
+
*
|
|
6
|
+
* 1. **Nothing calls `process.exit()`.** A command signals failure by throwing a
|
|
7
|
+
* `HarnessError` (or by returning an exit code from `run`); the entry point in
|
|
8
|
+
* `cli.ts` is the single place where a thrown value becomes `process.exitCode`.
|
|
9
|
+
* Exiting outright can truncate stdout mid-write when it is piped.
|
|
10
|
+
* 2. **Every non-zero exit carries a message naming its cause.** A bare non-zero exit
|
|
11
|
+
* with no explanation is indistinguishable from a crash to the run daemon.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* The exit codes this CLI produces. Callers that branch on the CLI's result — the run
|
|
15
|
+
* daemon, CI, the acceptance gates in `docs/development.md` — read these and nothing else.
|
|
16
|
+
*/
|
|
17
|
+
export const EXIT = {
|
|
18
|
+
/** `0` — success: the command did what it was asked to do, including `--help` and `--version`. */
|
|
19
|
+
OK: 0,
|
|
20
|
+
/**
|
|
21
|
+
* `1` — a command-level refusal or a failed check. Expected and actionable: an unknown
|
|
22
|
+
* command, bad usage, a `doctor` FAIL, a write refused by the re-run contract. Always
|
|
23
|
+
* accompanied by a message naming the cause.
|
|
24
|
+
*/
|
|
25
|
+
FAILURE: 1,
|
|
26
|
+
/**
|
|
27
|
+
* `2` — an unexpected internal fault: a thrown value the CLI did not model. Reaching this
|
|
28
|
+
* code means the CLI hit a bug, not that the adopting repository is misconfigured.
|
|
29
|
+
*/
|
|
30
|
+
INTERNAL: 2,
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* An error the CLI raised deliberately: it carries a message written for the person
|
|
34
|
+
* running the command and the exit code that message should produce.
|
|
35
|
+
*
|
|
36
|
+
* Throw this for anything the CLI anticipated (a refusal, a failed check, bad usage).
|
|
37
|
+
* Leave every other failure to propagate as-is — `cli.ts` reports those as `(unexpected)`
|
|
38
|
+
* and exits {@link EXIT.INTERNAL}, which is the signal that the fault is a CLI bug.
|
|
39
|
+
*/
|
|
40
|
+
export class HarnessError extends Error {
|
|
41
|
+
exitCode;
|
|
42
|
+
constructor(message, exitCode = EXIT.FAILURE) {
|
|
43
|
+
super(message);
|
|
44
|
+
this.exitCode = exitCode;
|
|
45
|
+
this.name = 'HarnessError';
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* The sentence every internal fault ends with: the one thing the reader most needs to know about a
|
|
50
|
+
* message they cannot act on, which is that they are not the one who has to.
|
|
51
|
+
*/
|
|
52
|
+
const INTERNAL_SUFFIX = 'this is a fault in this CLI rather than in the repository it was run against';
|
|
53
|
+
/**
|
|
54
|
+
* A fault in this CLI or in the assets it ships — a template disagreeing with the generator that
|
|
55
|
+
* renders it, a write request the engine cannot satisfy, a value two modules were supposed to agree
|
|
56
|
+
* on and did not. Exits {@link EXIT.INTERNAL}, with {@link INTERNAL_SUFFIX} appended.
|
|
57
|
+
*
|
|
58
|
+
* **Use this rather than `new HarnessError(…, EXIT.INTERNAL)` for anything the adopting repository
|
|
59
|
+
* cannot fix.** The two codes are read by different audiences — {@link EXIT.FAILURE} says "correct
|
|
60
|
+
* your configuration and run it again", {@link EXIT.INTERNAL} says "this build is broken" — and the
|
|
61
|
+
* trailing sentence is what makes the difference legible in the message as well as in the status.
|
|
62
|
+
* Passing a message that already carries its own version of that sentence would say it twice.
|
|
63
|
+
*
|
|
64
|
+
* It lives here rather than in each module that throws one because it was copied into five of them
|
|
65
|
+
* before it was written down once, and four of those copies had already drifted in punctuation from
|
|
66
|
+
* the fifth.
|
|
67
|
+
*/
|
|
68
|
+
export function internal(message) {
|
|
69
|
+
return new HarnessError(`${message}: ${INTERNAL_SUFFIX}`, EXIT.INTERNAL);
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/core/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH;;;GAGG;AACH,MAAM,CAAC,MAAM,IAAI,GAAG;IAClB,kGAAkG;IAClG,EAAE,EAAE,CAAC;IACL;;;;OAIG;IACH,OAAO,EAAE,CAAC;IACV;;;OAGG;IACH,QAAQ,EAAE,CAAC;CACH,CAAC;AASX;;;;;;;GAOG;AACH,MAAM,OAAO,YAAa,SAAQ,KAAK;IAG1B;IAFX,YACE,OAAe,EACN,WAAqB,IAAI,CAAC,OAAO;QAE1C,KAAK,CAAC,OAAO,CAAC,CAAC;QAFN,aAAQ,GAAR,QAAQ,CAAyB;QAG1C,IAAI,CAAC,IAAI,GAAG,cAAc,CAAC;IAC7B,CAAC;CACF;AAED;;;GAGG;AACH,MAAM,eAAe,GAAG,8EAA8E,CAAC;AAEvG;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,QAAQ,CAAC,OAAe;IACtC,OAAO,IAAI,YAAY,CAAC,GAAG,OAAO,KAAK,eAAe,EAAE,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;AAC3E,CAAC"}
|
package/dist/core/git.js
ADDED
|
@@ -0,0 +1,537 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Every `git` invocation that reads repository **state or a path**, in one module.
|
|
3
|
+
*
|
|
4
|
+
* **The rule this module exists to enforce: no other module probes git for the state of a
|
|
5
|
+
* repository or for a path derived from one — it calls a function here.** One home means one
|
|
6
|
+
* definition of each probe, one place a review checks that a probe stayed read-only and issued
|
|
7
|
+
* bare, and one place a new invocation has to justify itself.
|
|
8
|
+
*
|
|
9
|
+
* **The single deliberate exception is `generators/githooks.ts`**, which carries its own private
|
|
10
|
+
* `runGit` for exactly two calls: reading `core.hooksPath` and writing it. Those read and write a
|
|
11
|
+
* repository **setting** rather than state or a path — the CLI's only write to git configuration —
|
|
12
|
+
* and that module's own header argues the split under "Where the filesystem and git are touched,
|
|
13
|
+
* and why they are split". It is an exception with a boundary, not a precedent: a third invocation
|
|
14
|
+
* anywhere in the package belongs here.
|
|
15
|
+
*
|
|
16
|
+
* Two functions below are a **collapsing view over another's probe rather than a probe of their
|
|
17
|
+
* own** — {@link resolveRepoRoot} over {@link probeRepoRoot}, {@link currentBranch} over
|
|
18
|
+
* {@link checkedOutBranch}. Neither adds a git invocation and neither adds an invariant: the rule
|
|
19
|
+
* above is about the invocation, and each pair issues one.
|
|
20
|
+
*
|
|
21
|
+
* Two invariants hold in every git call below:
|
|
22
|
+
*
|
|
23
|
+
* 1. **`execFileSync` with an argv array, never a shell string** — no `shell: true`, no
|
|
24
|
+
* interpolation, so a path containing a space or a quote cannot become two arguments.
|
|
25
|
+
* 2. **The repository-root probe is issued bare** — the literal arguments
|
|
26
|
+
* `rev-parse --show-toplevel`, with nothing added. An unattended run's permission profile
|
|
27
|
+
* can allow-list a literal read-only git command, while the same command wrapped in a shell
|
|
28
|
+
* substitution or embedded in a compound statement is a different string and stalls on a
|
|
29
|
+
* prompt (`docs/config.md` §1).
|
|
30
|
+
*
|
|
31
|
+
* Most of what follows is read-only. The two exceptions — {@link initRepository} and
|
|
32
|
+
* {@link commitAll} — are the only functions in the CLI that mutate a repository's history, and
|
|
33
|
+
* each is called from one place, behind an explicit adopter decision.
|
|
34
|
+
*/
|
|
35
|
+
import { execFileSync } from 'node:child_process';
|
|
36
|
+
import { HarnessError } from './errors.js';
|
|
37
|
+
/**
|
|
38
|
+
* The bare repository-root probe, as one frozen literal. Kept as a named constant so a review
|
|
39
|
+
* can see at a glance that no flag was added to it (`--path-format`, `-C`, `--git-dir`) — see
|
|
40
|
+
* invariant 2 in the module header.
|
|
41
|
+
*/
|
|
42
|
+
const GIT_REPO_ROOT_ARGS = Object.freeze(['rev-parse', '--show-toplevel']);
|
|
43
|
+
/** The checked-out-branch probe, read-only and with no flag beyond the two that name the branch. */
|
|
44
|
+
const GIT_CURRENT_BRANCH_ARGS = Object.freeze(['rev-parse', '--abbrev-ref', 'HEAD']);
|
|
45
|
+
/**
|
|
46
|
+
* The same question asked of `HEAD` itself rather than of the commit it points at: the branch `HEAD`
|
|
47
|
+
* names, answered without requiring a commit to exist on it.
|
|
48
|
+
*
|
|
49
|
+
* It is here because the probe above cannot answer in the state every repository with no commit is
|
|
50
|
+
* in at the moment detection runs — the one `init --git-init` just created, and the one an adopter
|
|
51
|
+
* made and has not committed into. `HEAD` is unborn, `rev-parse --abbrev-ref HEAD` exits 128, and
|
|
52
|
+
* the branch the first commit is about to land on is exactly the value that has to be configured. `--quiet` keeps a detached `HEAD` an exit status rather than an error on stderr.
|
|
53
|
+
*/
|
|
54
|
+
const GIT_SYMBOLIC_HEAD_ARGS = Object.freeze(['symbolic-ref', '--quiet', '--short', 'HEAD']);
|
|
55
|
+
/**
|
|
56
|
+
* The worktree inventory probe, issued bare: the two literal arguments and nothing added.
|
|
57
|
+
*
|
|
58
|
+
* `--porcelain` is deliberately absent. The only caller asks whether this git can answer the
|
|
59
|
+
* question at all, not what the answer says, and the plain form is the one an unattended run's
|
|
60
|
+
* permission profile is most likely to already allow as a literal.
|
|
61
|
+
*/
|
|
62
|
+
const GIT_WORKTREE_LIST_ARGS = Object.freeze(['worktree', 'list']);
|
|
63
|
+
/**
|
|
64
|
+
* "Does `HEAD` name a commit" — `--quiet` so an unborn HEAD prints nothing rather than an error,
|
|
65
|
+
* leaving the exit status as the whole answer.
|
|
66
|
+
*/
|
|
67
|
+
const GIT_HAS_COMMITS_ARGS = Object.freeze(['rev-parse', '--verify', '--quiet', 'HEAD']);
|
|
68
|
+
/**
|
|
69
|
+
* Repository creation, as the literal single argument.
|
|
70
|
+
*
|
|
71
|
+
* **No `--initial-branch`.** git's own `init.defaultBranch` decides the name, and detection reads
|
|
72
|
+
* that name back off the unborn `HEAD` this leaves behind ({@link checkedOutBranch}); passing a name
|
|
73
|
+
* here would pre-empt the value the detection is supposed to read.
|
|
74
|
+
*/
|
|
75
|
+
const GIT_INIT_ARGS = Object.freeze(['init']);
|
|
76
|
+
/** Where `origin` says its own default branch points, or nothing when no remote HEAD is known. */
|
|
77
|
+
const GIT_REMOTE_HEAD_ARGS = Object.freeze([
|
|
78
|
+
'symbolic-ref',
|
|
79
|
+
'--quiet',
|
|
80
|
+
'--short',
|
|
81
|
+
'refs/remotes/origin/HEAD',
|
|
82
|
+
]);
|
|
83
|
+
/** The configured name `git init` would give a new repository's first branch, when one is set. */
|
|
84
|
+
const GIT_INIT_DEFAULT_BRANCH_ARGS = Object.freeze(['config', '--get', 'init.defaultBranch']);
|
|
85
|
+
/** The remote inventory probe, issued bare: names only, one per line, with no flag added. */
|
|
86
|
+
const GIT_REMOTES_ARGS = Object.freeze(['remote']);
|
|
87
|
+
/**
|
|
88
|
+
* The ref-existence probe, minus the ref. The full ref name is appended as **one** argv element by
|
|
89
|
+
* {@link refResolves}; `--quiet` makes the exit status the whole answer.
|
|
90
|
+
*/
|
|
91
|
+
const GIT_VERIFY_REF_ARGS = Object.freeze(['rev-parse', '--verify', '--quiet']);
|
|
92
|
+
/**
|
|
93
|
+
* The commit-counting probe, minus the range. The `<base>..<tip>` range is appended as **one** argv
|
|
94
|
+
* element by {@link commitsAhead} (module header, invariant 1), followed by the pathspec separator
|
|
95
|
+
* below.
|
|
96
|
+
*/
|
|
97
|
+
const GIT_COUNT_RANGE_ARGS = Object.freeze(['rev-list', '--count']);
|
|
98
|
+
/**
|
|
99
|
+
* What separates revisions from paths on a git command line, appended after the range so a range
|
|
100
|
+
* that happens to spell a path in the working tree cannot be read as one.
|
|
101
|
+
*/
|
|
102
|
+
const PATHSPEC_SEPARATOR = '--';
|
|
103
|
+
/**
|
|
104
|
+
* The ignore probe, minus the path. `--quiet` makes the exit status the whole answer, and
|
|
105
|
+
* {@link PATHSPEC_SEPARATOR} is part of the constant rather than appended by the caller so the path
|
|
106
|
+
* is always passed after it — a path beginning with `-` can then never be read as an option.
|
|
107
|
+
*/
|
|
108
|
+
const GIT_CHECK_IGNORE_ARGS = Object.freeze([
|
|
109
|
+
'check-ignore',
|
|
110
|
+
'--quiet',
|
|
111
|
+
PATHSPEC_SEPARATOR,
|
|
112
|
+
]);
|
|
113
|
+
/** The prefix `git` prints on a remote-tracking branch, stripped before the name is reported. */
|
|
114
|
+
const ORIGIN_PREFIX = 'origin/';
|
|
115
|
+
/**
|
|
116
|
+
* Run `git` with a fixed argument vector and return its trimmed stdout.
|
|
117
|
+
*
|
|
118
|
+
* stdin is closed and stderr is captured rather than inherited: a git failure is turned into a
|
|
119
|
+
* `HarnessError` with a message written for the person running the command, so the raw git
|
|
120
|
+
* text never interleaves with the reporter's output.
|
|
121
|
+
*/
|
|
122
|
+
function runGit(args, cwd) {
|
|
123
|
+
return execFileSync('git', [...args], {
|
|
124
|
+
cwd,
|
|
125
|
+
encoding: 'utf8',
|
|
126
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
127
|
+
windowsHide: true,
|
|
128
|
+
}).trim();
|
|
129
|
+
}
|
|
130
|
+
/** True when a thrown value is a failure to spawn `git` at all, rather than a non-zero status. */
|
|
131
|
+
function isMissingGit(error) {
|
|
132
|
+
return error?.code === 'ENOENT';
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* One captured stream off a thrown `execFileSync` result, decoded and flattened to a single line.
|
|
136
|
+
*
|
|
137
|
+
* git answers in paragraphs (`On branch main\nnothing to commit, working tree clean`) and every
|
|
138
|
+
* consumer of this text is a one-line warning or check detail, so the newlines are collapsed here
|
|
139
|
+
* rather than in each caller. Nothing is dropped — only the line breaks become spaces.
|
|
140
|
+
*/
|
|
141
|
+
function capturedStream(error, stream) {
|
|
142
|
+
const value = error?.[stream];
|
|
143
|
+
const text = typeof value === 'string' ? value : value instanceof Buffer ? value.toString('utf8') : '';
|
|
144
|
+
return text.replace(/\s+/g, ' ').trim();
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* What to tell the adopter about a failed invocation: **git's own account of it**, which is the
|
|
148
|
+
* sentence that names the actual cause and the only one worth putting in front of them.
|
|
149
|
+
*
|
|
150
|
+
* stderr first — that is where a refusal lands (`Author identity unknown`, a rejecting hook).
|
|
151
|
+
* stdout second, because `git commit` reports the one non-failure failure this CLI actually meets,
|
|
152
|
+
* `nothing to commit, working tree clean`, on stdout while still exiting non-zero. The thrown
|
|
153
|
+
* value's own message is the last resort: it is `Command failed: git commit -m <message>`, which
|
|
154
|
+
* names no cause and echoes the whole commit message back.
|
|
155
|
+
*
|
|
156
|
+
* A missing `git` is spelled out rather than passed through, because the raw `ENOENT` text names
|
|
157
|
+
* `spawnSync` and a file descriptor rather than the thing the reader has to fix.
|
|
158
|
+
*/
|
|
159
|
+
function gitFailureDetail(error) {
|
|
160
|
+
if (isMissingGit(error))
|
|
161
|
+
return 'git is not on PATH';
|
|
162
|
+
const stderr = capturedStream(error, 'stderr');
|
|
163
|
+
if (stderr !== '')
|
|
164
|
+
return stderr;
|
|
165
|
+
const stdout = capturedStream(error, 'stdout');
|
|
166
|
+
if (stdout !== '')
|
|
167
|
+
return stdout;
|
|
168
|
+
return error instanceof Error ? error.message : String(error);
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* The substring every wording of git's "there is no repository here" carries — the parenthesised
|
|
172
|
+
* `(or any of the parent directories)` form and the `--git-dir` form alike. Matched on stderr
|
|
173
|
+
* because `rev-parse --show-toplevel` exits non-zero for that answer and for every other refusal
|
|
174
|
+
* alike, and only this one leaves the directory free for `init` to offer to create one in.
|
|
175
|
+
*/
|
|
176
|
+
const NOT_A_REPOSITORY_STDERR = 'not a git repository';
|
|
177
|
+
/** The one wording of the no-repository refusal, so both branches that reach it read identically. */
|
|
178
|
+
const NOT_A_REPOSITORY_MESSAGE = (cwd) => `not inside a git repository: ${cwd}`;
|
|
179
|
+
/**
|
|
180
|
+
* The repository root containing `cwd`, or why there is not one — from the same bare
|
|
181
|
+
* `rev-parse --show-toplevel` as {@link resolveRepoRoot}, which is implemented on top of this.
|
|
182
|
+
*
|
|
183
|
+
* The carried messages are the exact strings {@link resolveRepoRoot} throws, so a caller that
|
|
184
|
+
* cannot act on the distinction can report the failure identically to a caller that never probed.
|
|
185
|
+
*/
|
|
186
|
+
export function probeRepoRoot(cwd) {
|
|
187
|
+
let output;
|
|
188
|
+
try {
|
|
189
|
+
output = runGit(GIT_REPO_ROOT_ARGS, cwd);
|
|
190
|
+
}
|
|
191
|
+
catch (error) {
|
|
192
|
+
if (isMissingGit(error)) {
|
|
193
|
+
return {
|
|
194
|
+
kind: 'no-git',
|
|
195
|
+
message: `git is not on PATH, so the repository root of ${cwd} cannot be resolved`,
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
const detail = gitFailureDetail(error);
|
|
199
|
+
if (detail.toLowerCase().includes(NOT_A_REPOSITORY_STDERR)) {
|
|
200
|
+
return { kind: 'not-a-repository', message: NOT_A_REPOSITORY_MESSAGE(cwd) };
|
|
201
|
+
}
|
|
202
|
+
return {
|
|
203
|
+
kind: 'git-failed',
|
|
204
|
+
message: `git could not resolve the repository root of ${cwd}: ${detail}`,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
if (output === '')
|
|
208
|
+
return { kind: 'not-a-repository', message: NOT_A_REPOSITORY_MESSAGE(cwd) };
|
|
209
|
+
return { kind: 'repository', root: output };
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* `<repo_root>` — the root of the repository containing `cwd`, from a bare
|
|
213
|
+
* `git rev-parse --show-toplevel`.
|
|
214
|
+
*
|
|
215
|
+
* In a git worktree this is the **worktree's** root, not the main checkout's, which is the
|
|
216
|
+
* whole point of resolving it per run: a worktree run must write into its own checkout.
|
|
217
|
+
*
|
|
218
|
+
* Throws a `HarnessError` when `cwd` is not inside a repository, or when `git` is not on PATH.
|
|
219
|
+
* Both are adopter-fixable, so both carry the default exit code.
|
|
220
|
+
*
|
|
221
|
+
* It is a thin refusal on top of {@link probeRepoRoot} so that the root probe is issued from
|
|
222
|
+
* exactly one place in the package, whether or not the caller can act on the distinction.
|
|
223
|
+
*/
|
|
224
|
+
export function resolveRepoRoot(cwd) {
|
|
225
|
+
const probe = probeRepoRoot(cwd);
|
|
226
|
+
if (probe.kind === 'repository')
|
|
227
|
+
return probe.root;
|
|
228
|
+
throw new HarnessError(probe.message);
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* What `HEAD` names in this checkout — the three answers of {@link HeadProbe}, from the same two
|
|
232
|
+
* probes {@link currentBranch} is implemented on top of, and **no third git invocation**.
|
|
233
|
+
*
|
|
234
|
+
* {@link GIT_CURRENT_BRANCH_ARGS} answers first, and the literal `HEAD` it prints for a detached
|
|
235
|
+
* checkout is what `'detached'` is decided on; {@link GIT_SYMBOLIC_HEAD_ARGS} answers only where
|
|
236
|
+
* that one throws, which is the repository whose `HEAD` is unborn. It never throws itself; a caller
|
|
237
|
+
* that needs the repository at all has already resolved its root through {@link resolveRepoRoot},
|
|
238
|
+
* which does throw.
|
|
239
|
+
*
|
|
240
|
+
* It lives here rather than in the generator that wants it, for the reason in the module header:
|
|
241
|
+
* every repository-state probe has one definition and one place a review checks that it stayed
|
|
242
|
+
* read-only.
|
|
243
|
+
*/
|
|
244
|
+
export function checkedOutBranch(repoRoot) {
|
|
245
|
+
let output;
|
|
246
|
+
try {
|
|
247
|
+
output = runGit(GIT_CURRENT_BRANCH_ARGS, repoRoot);
|
|
248
|
+
}
|
|
249
|
+
catch {
|
|
250
|
+
// An unborn HEAD is not a detached one: `symbolic-ref` still names the branch the first commit
|
|
251
|
+
// will create. A detached HEAD never reaches here — the probe above succeeds and prints the
|
|
252
|
+
// literal `HEAD` — and a repository the second probe cannot answer for either is `'none'`.
|
|
253
|
+
try {
|
|
254
|
+
output = runGit(GIT_SYMBOLIC_HEAD_ARGS, repoRoot);
|
|
255
|
+
}
|
|
256
|
+
catch {
|
|
257
|
+
return { kind: 'none' };
|
|
258
|
+
}
|
|
259
|
+
return output === '' ? { kind: 'none' } : { kind: 'branch', name: output };
|
|
260
|
+
}
|
|
261
|
+
// `HEAD` is what the first probe prints for a detached checkout: a literal, not a branch name.
|
|
262
|
+
if (output === 'HEAD')
|
|
263
|
+
return { kind: 'detached' };
|
|
264
|
+
return output === '' ? { kind: 'none' } : { kind: 'branch', name: output };
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* The name of the checked-out branch, or `undefined` when there is not one to name — the collapsing
|
|
268
|
+
* view over {@link checkedOutBranch}, for the caller that cannot act on *why* there is no name.
|
|
269
|
+
*
|
|
270
|
+
* `'detached'` and `'none'` both come back as `undefined`, because they mean the same thing to
|
|
271
|
+
* `doctor`'s `--test-notification` probe: no branch to report here, and the configured
|
|
272
|
+
* `defaultBranch` stands in. The caller that can act on the difference is `init`'s default-branch
|
|
273
|
+
* detection, which asks {@link checkedOutBranch} directly so its note can name which one it met.
|
|
274
|
+
*/
|
|
275
|
+
export function currentBranch(repoRoot) {
|
|
276
|
+
const probe = checkedOutBranch(repoRoot);
|
|
277
|
+
return probe.kind === 'branch' ? probe.name : undefined;
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* The checkouts git reports for this repository, one line each, or `undefined` when the probe did
|
|
281
|
+
* not answer — a git too old for the subcommand, a repository state it refuses to list, or no `git`
|
|
282
|
+
* at all.
|
|
283
|
+
*
|
|
284
|
+
* `undefined` and an empty answer are the same result on purpose: `git worktree list` prints the
|
|
285
|
+
* main working copy even when there is only one, so nothing to report means the probe did not work
|
|
286
|
+
* rather than that there are no checkouts. The one caller is `doctor`, which asks whether
|
|
287
|
+
* worktree-based runs can be prepared here and reports a `warn` either way, so the distinction
|
|
288
|
+
* between the ways it can fail buys nothing.
|
|
289
|
+
*
|
|
290
|
+
* It lives here rather than in that check, for the reason in the module header: every
|
|
291
|
+
* repository-state probe has one definition and one place a review checks that it stayed read-only.
|
|
292
|
+
*/
|
|
293
|
+
export function worktreeList(repoRoot) {
|
|
294
|
+
let output;
|
|
295
|
+
try {
|
|
296
|
+
output = runGit(GIT_WORKTREE_LIST_ARGS, repoRoot);
|
|
297
|
+
}
|
|
298
|
+
catch {
|
|
299
|
+
return undefined;
|
|
300
|
+
}
|
|
301
|
+
const lines = output
|
|
302
|
+
.split('\n')
|
|
303
|
+
.map((line) => line.trim())
|
|
304
|
+
.filter((line) => line !== '');
|
|
305
|
+
return lines.length === 0 ? undefined : lines;
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Whether this repository has any commit at all.
|
|
309
|
+
*
|
|
310
|
+
* **An unborn HEAD is the answer, not an error**, so every non-zero status is `false`: a freshly
|
|
311
|
+
* `git init`'d repository is the normal state at the moment `init` runs, and the callers that ask
|
|
312
|
+
* — the first-commit path and the default-branch check — both have something correct to do with a
|
|
313
|
+
* `false` and nothing correct to do with a throw.
|
|
314
|
+
*/
|
|
315
|
+
export function hasCommits(repoRoot) {
|
|
316
|
+
try {
|
|
317
|
+
runGit(GIT_HAS_COMMITS_ARGS, repoRoot);
|
|
318
|
+
return true;
|
|
319
|
+
}
|
|
320
|
+
catch {
|
|
321
|
+
return false;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Create a repository in `dir` — the CLI's one call to `git init`, made only after the adopter has
|
|
326
|
+
* said yes (a flag, or a prompt that has one).
|
|
327
|
+
*
|
|
328
|
+
* Issued bare, for the reason on {@link GIT_INIT_ARGS}: the branch name is git's own
|
|
329
|
+
* `init.defaultBranch` to decide, and detection reads it back rather than the CLI dictating it.
|
|
330
|
+
*
|
|
331
|
+
* Throws a `HarnessError` naming `dir` and git's own stderr. This one throws, unlike
|
|
332
|
+
* {@link commitAll}, because nothing has been wired yet when it runs: a repository that could not
|
|
333
|
+
* be created has no partial success to preserve.
|
|
334
|
+
*/
|
|
335
|
+
export function initRepository(dir) {
|
|
336
|
+
try {
|
|
337
|
+
runGit(GIT_INIT_ARGS, dir);
|
|
338
|
+
}
|
|
339
|
+
catch (error) {
|
|
340
|
+
throw new HarnessError(`git init failed in ${dir}: ${gitFailureDetail(error)}`);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Stage everything and commit it: `add -A`, then `commit -m <message>` with the message as a single
|
|
345
|
+
* argv element.
|
|
346
|
+
*
|
|
347
|
+
* **This never throws.** Its caller has already written the adopter's wiring by the time it runs, so
|
|
348
|
+
* a throw here would report a failed command for a run that succeeded — the files are on disk and
|
|
349
|
+
* correct either way. A refusal git can state (`Author identity unknown`, an empty index, a hook
|
|
350
|
+
* that rejected it) comes back as `committed: false` with git's own trimmed stderr, which the
|
|
351
|
+
* caller warns with and carries on.
|
|
352
|
+
*/
|
|
353
|
+
export function commitAll(repoRoot, message) {
|
|
354
|
+
try {
|
|
355
|
+
runGit(['add', '-A'], repoRoot);
|
|
356
|
+
}
|
|
357
|
+
catch (error) {
|
|
358
|
+
return { committed: false, reason: gitFailureDetail(error) };
|
|
359
|
+
}
|
|
360
|
+
try {
|
|
361
|
+
runGit(['commit', '-m', message], repoRoot);
|
|
362
|
+
}
|
|
363
|
+
catch (error) {
|
|
364
|
+
return { committed: false, reason: gitFailureDetail(error) };
|
|
365
|
+
}
|
|
366
|
+
return { committed: true };
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* The branch `origin/HEAD` points at, with the `origin/` prefix stripped — the first and most
|
|
370
|
+
* authoritative rung of default-branch detection, because it is the remote's own answer rather than
|
|
371
|
+
* a guess from this checkout.
|
|
372
|
+
*
|
|
373
|
+
* `undefined` on any non-zero status, which covers both the repository with no remote and the
|
|
374
|
+
* remote whose HEAD was never fetched (a clone made with `--no-tags`, a manually added remote).
|
|
375
|
+
* Neither is a fault: the caller falls through to the next rung.
|
|
376
|
+
*/
|
|
377
|
+
export function remoteHeadBranch(repoRoot) {
|
|
378
|
+
let output;
|
|
379
|
+
try {
|
|
380
|
+
output = runGit(GIT_REMOTE_HEAD_ARGS, repoRoot);
|
|
381
|
+
}
|
|
382
|
+
catch {
|
|
383
|
+
return undefined;
|
|
384
|
+
}
|
|
385
|
+
const branch = output.startsWith(ORIGIN_PREFIX) ? output.slice(ORIGIN_PREFIX.length) : output;
|
|
386
|
+
return branch === '' ? undefined : branch;
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* The configured `init.defaultBranch`, or `undefined` when it is unset — the last rung of
|
|
390
|
+
* default-branch detection, and the name git itself would have given a repository
|
|
391
|
+
* {@link initRepository} created.
|
|
392
|
+
*
|
|
393
|
+
* Unset is a non-zero status from `config --get`, which is why every failure collapses to
|
|
394
|
+
* `undefined` rather than being distinguished: there is nothing else this probe can fail at that
|
|
395
|
+
* the caller would treat differently.
|
|
396
|
+
*/
|
|
397
|
+
export function configuredInitDefaultBranch(repoRoot) {
|
|
398
|
+
let output;
|
|
399
|
+
try {
|
|
400
|
+
output = runGit(GIT_INIT_DEFAULT_BRANCH_ARGS, repoRoot);
|
|
401
|
+
}
|
|
402
|
+
catch {
|
|
403
|
+
return undefined;
|
|
404
|
+
}
|
|
405
|
+
return output === '' ? undefined : output;
|
|
406
|
+
}
|
|
407
|
+
/**
|
|
408
|
+
* The names of the remotes this repository has configured, in git's own order, or an empty list
|
|
409
|
+
* when it has none.
|
|
410
|
+
*
|
|
411
|
+
* **No remote is the answer, not an error**, the same discipline {@link hasCommits} states: a
|
|
412
|
+
* repository that has never had one is an ordinary state, and the caller — `doctor`'s remote check
|
|
413
|
+
* — grades that state itself. Every non-zero status therefore collapses to the empty list, because
|
|
414
|
+
* a probe that failed some other way leaves that caller with nothing different to do.
|
|
415
|
+
*/
|
|
416
|
+
export function configuredRemotes(repoRoot) {
|
|
417
|
+
let output;
|
|
418
|
+
try {
|
|
419
|
+
output = runGit(GIT_REMOTES_ARGS, repoRoot);
|
|
420
|
+
}
|
|
421
|
+
catch {
|
|
422
|
+
return [];
|
|
423
|
+
}
|
|
424
|
+
return output
|
|
425
|
+
.split('\n')
|
|
426
|
+
.map((line) => line.trim())
|
|
427
|
+
.filter((line) => line !== '');
|
|
428
|
+
}
|
|
429
|
+
/** True when `ref` names something this repository can resolve. The ref is one argv element. */
|
|
430
|
+
function refResolves(repoRoot, ref) {
|
|
431
|
+
try {
|
|
432
|
+
runGit([...GIT_VERIFY_REF_ARGS, ref], repoRoot);
|
|
433
|
+
return true;
|
|
434
|
+
}
|
|
435
|
+
catch {
|
|
436
|
+
return false;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
/**
|
|
440
|
+
* Where a branch name resolves in this repository: a local branch, a remote-tracking ref under
|
|
441
|
+
* `origin/`, or nowhere.
|
|
442
|
+
*
|
|
443
|
+
* Both spellings are checked because a configured integration line is legitimately present as
|
|
444
|
+
* either — a fresh clone with a single checked-out branch has the rest only as remote-tracking
|
|
445
|
+
* refs, and a repository that has never had a remote has only local ones. `'none'` is the answer
|
|
446
|
+
* that means the configured name is wrong, and it is the only one a caller grades as a failure.
|
|
447
|
+
*
|
|
448
|
+
* The full ref names are built by string join and passed as **one argv element each** (module
|
|
449
|
+
* header, invariant 1). Prefixing with `refs/heads/` also means a branch name that starts with `-`
|
|
450
|
+
* can never be read as an option.
|
|
451
|
+
*
|
|
452
|
+
* It answers where a name resolves **first**, so it cannot be asked whether a remote-tracking ref
|
|
453
|
+
* exists: the ordinary repository has both spellings and reads `'local'` here. That question is
|
|
454
|
+
* {@link remoteTrackingBranchResolves}'.
|
|
455
|
+
*/
|
|
456
|
+
export function branchResolves(repoRoot, branch) {
|
|
457
|
+
if (refResolves(repoRoot, `refs/heads/${branch}`))
|
|
458
|
+
return 'local';
|
|
459
|
+
if (refResolves(repoRoot, `refs/remotes/${ORIGIN_PREFIX}${branch}`))
|
|
460
|
+
return 'remote';
|
|
461
|
+
return 'none';
|
|
462
|
+
}
|
|
463
|
+
/**
|
|
464
|
+
* Whether `origin/<branch>` is a remote-tracking ref in this repository.
|
|
465
|
+
*
|
|
466
|
+
* A separate question from {@link branchResolves}, not a narrowing of it: that one stops at the
|
|
467
|
+
* local spelling, which every checkout of its own integration line has, so it answers `'local'`
|
|
468
|
+
* exactly where this has to answer `true`. What reads this is the precondition
|
|
469
|
+
* `create-worktree.sh` branches a run's checkout from, and only the remote-tracking ref satisfies
|
|
470
|
+
* it — a local branch of the same name does not.
|
|
471
|
+
*
|
|
472
|
+
* The ref is built by string join and passed as **one argv element** (module header, invariant 1).
|
|
473
|
+
*/
|
|
474
|
+
export function remoteTrackingBranchResolves(repoRoot, branch) {
|
|
475
|
+
return refResolves(repoRoot, `refs/remotes/${ORIGIN_PREFIX}${branch}`);
|
|
476
|
+
}
|
|
477
|
+
/**
|
|
478
|
+
* How many commits `tipRef` has that `baseRef` does not — `rev-list --count <baseRef>..<tipRef>`,
|
|
479
|
+
* or `undefined` when the count could not be taken.
|
|
480
|
+
*
|
|
481
|
+
* **Read-only, and it issues no fetch**, so a caller's answer is the same offline and `doctor`'s
|
|
482
|
+
* "writes nothing beyond the writability probe's temp file" contract holds through it. The two refs
|
|
483
|
+
* are compared exactly as they stand in this repository; making them current is the caller's remedy
|
|
484
|
+
* to name, never this probe's to do.
|
|
485
|
+
*
|
|
486
|
+
* `undefined` covers every non-zero status — a ref that does not resolve, a range git read as
|
|
487
|
+
* ambiguous, no `git` at all — on the discipline {@link configuredRemotes} states: a probe that
|
|
488
|
+
* failed some other way leaves the caller with nothing different to do. It also covers output that
|
|
489
|
+
* is not a count, which no answered invocation produces and which no caller could act on either.
|
|
490
|
+
*
|
|
491
|
+
* The range is built by string join and passed as **one argv element** (module header, invariant 1),
|
|
492
|
+
* with {@link PATHSPEC_SEPARATOR} after it.
|
|
493
|
+
*/
|
|
494
|
+
export function commitsAhead(repoRoot, baseRef, tipRef) {
|
|
495
|
+
let output;
|
|
496
|
+
try {
|
|
497
|
+
output = runGit([...GIT_COUNT_RANGE_ARGS, `${baseRef}..${tipRef}`, PATHSPEC_SEPARATOR], repoRoot);
|
|
498
|
+
}
|
|
499
|
+
catch {
|
|
500
|
+
return undefined;
|
|
501
|
+
}
|
|
502
|
+
if (output === '')
|
|
503
|
+
return undefined;
|
|
504
|
+
const count = Number(output);
|
|
505
|
+
return Number.isInteger(count) && count >= 0 ? count : undefined;
|
|
506
|
+
}
|
|
507
|
+
/**
|
|
508
|
+
* Whether git excludes `repoRelativePath` in this working tree — `true` ignored, `false` not, and
|
|
509
|
+
* `undefined` when git did not answer.
|
|
510
|
+
*
|
|
511
|
+
* **The third value is not a boolean and must not be collapsed into one.** `git check-ignore
|
|
512
|
+
* --quiet` exits **0** when a rule matches, **1** when none does and **128** when it could not run
|
|
513
|
+
* at all, so reading the third status as either verdict would turn a broken invocation into an
|
|
514
|
+
* answer — which is exactly the fault `doctor`'s `ignore-rules` check exists to stop committing.
|
|
515
|
+
* Each caller decides what an unanswerable probe means for it: `ignore-rules` grades the
|
|
516
|
+
* `undefined` and says it did not measure, and `layer-drift` excludes only on a definite `true`, so
|
|
517
|
+
* a candidate git could not resolve stays in the report.
|
|
518
|
+
*
|
|
519
|
+
* The answer is the **effective** one — every ignore source in force here, and the index with them,
|
|
520
|
+
* so a path already tracked comes back `false` because `git add` of it succeeds. That is the
|
|
521
|
+
* question asked: whether a path the harness cares about is excluded right now, not which pattern
|
|
522
|
+
* in which file decided it. **Read-only**, like every probe above it: it resolves a name against
|
|
523
|
+
* rules and touches neither the index nor the working tree.
|
|
524
|
+
*
|
|
525
|
+
* The path is appended as **one argv element**, after {@link PATHSPEC_SEPARATOR} (module header,
|
|
526
|
+
* invariant 1).
|
|
527
|
+
*/
|
|
528
|
+
export function pathIsIgnored(repoRoot, repoRelativePath) {
|
|
529
|
+
try {
|
|
530
|
+
runGit([...GIT_CHECK_IGNORE_ARGS, repoRelativePath], repoRoot);
|
|
531
|
+
return true;
|
|
532
|
+
}
|
|
533
|
+
catch (error) {
|
|
534
|
+
return error?.status === 1 ? false : undefined;
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
//# sourceMappingURL=git.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"git.js","sourceRoot":"","sources":["../../src/core/git.ts"],"names":[],"mappings":"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"}
|