@esneiderbravo/speclaw 0.2.0 → 0.3.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/README.md +17 -3
- package/dist/cli/commands/init.js +6 -0
- package/dist/cli/commands/update.js +5 -0
- package/dist/cli/lib/untrack.js +25 -0
- package/dist/modules/foundation/assets/docs/standards/lawbook.template.md +3 -1
- package/dist/modules/foundation/assets/docs/standards/testing-standards.template.md +12 -3
- package/dist/modules/foundation/scaffold.js +4 -0
- package/dist/modules/lawbook/assets/rules/spec-reports-disciplines.md +78 -0
- package/dist/modules/lawbook/assets/rules/spec-tasks-mandatory-steps.md +5 -3
- package/dist/modules/lawbook/assets/skills/build/SKILL.md +15 -3
- package/dist/modules/lawbook/assets/skills/draft/SKILL.md +5 -2
- package/dist/modules/lawbook/engine.js +1 -1
- package/dist/shared/git.js +39 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -162,6 +162,19 @@ still use Compass and the lawbook engine by calling the CLI from its shell.
|
|
|
162
162
|
<img src="https://raw.githubusercontent.com/esneiderbravo/speclaw/main/brand/terminal-tree.png" width="800" alt="what speclaw writes into your project">
|
|
163
163
|
</p>
|
|
164
164
|
|
|
165
|
+
**Committed vs. local.** Your **personalized source** is committed — `LAWS.md`,
|
|
166
|
+
`CLAUDE.md`, `AGENTS.md`, `docs/standards/*`, `docs/compass.md`, and the
|
|
167
|
+
`lawbook/` workspace. speclaw's **regenerable workflow content is local, not
|
|
168
|
+
committed**: only `ai-specs/` (skills, commands, rules, agent packs, and its
|
|
169
|
+
`.speclaw.json` manifest) is gitignored, because `init`/`update` reconstruct it
|
|
170
|
+
from the package — like a dependency. So **after cloning a speclaw project, run
|
|
171
|
+
`speclaw init` (or `speclaw update`)** to regenerate `ai-specs/` locally, which
|
|
172
|
+
the agent IDE symlinks point into. If a project committed `ai-specs/` before
|
|
173
|
+
this behavior existed, `init`/`update` print the exact `git rm -r --cached
|
|
174
|
+
ai-specs` command to stop tracking it (they never touch your git index
|
|
175
|
+
themselves). The agent directories (`.claude/`, `.cursor/`, …) are **left to
|
|
176
|
+
you** — commit your own skills and commands there if you want to.
|
|
177
|
+
|
|
165
178
|
<br/>
|
|
166
179
|
|
|
167
180
|
## <img src="https://raw.githubusercontent.com/esneiderbravo/speclaw/main/brand/diamond.png" height="20" alt="◆" align="absmiddle"> Philosophy — why "laws"?
|
|
@@ -189,9 +202,10 @@ without a re-init, splitting files by who owns them:
|
|
|
189
202
|
|
|
190
203
|
- **Managed files** (speclaw's workflow machinery — the skills, commands, rules,
|
|
191
204
|
and agent packs under `ai-specs/`) are **refreshed** to the new version, so
|
|
192
|
-
improvements actually reach your project.
|
|
193
|
-
|
|
194
|
-
|
|
205
|
+
improvements actually reach your project. They live locally (gitignored, see
|
|
206
|
+
*What lands in your project*) and are reconstructed from the package. If you
|
|
207
|
+
edited one locally, `update` reports the overwrite; pass `--backup` to keep a
|
|
208
|
+
`<file>.bak` (itself gitignored) before it is refreshed.
|
|
195
209
|
- **Personalized files** (your constitution and standards — `CLAUDE.md`,
|
|
196
210
|
`AGENTS.md`, `LAWS.md`, `docs/standards/*`, `docs/compass.md`,
|
|
197
211
|
`lawbook/config.yaml`) are **never auto-edited**. When a release changes their
|
|
@@ -9,6 +9,7 @@ import { loadPacks } from "../../modules/tools/packs.js";
|
|
|
9
9
|
import { list } from "../lib/args.js";
|
|
10
10
|
import { ui, c, banner, renderProgress, clearProgress } from "../lib/ui.js";
|
|
11
11
|
import { checkForUpdates } from "../lib/update-check.js";
|
|
12
|
+
import { reportTrackedLocalContent } from "../lib/untrack.js";
|
|
12
13
|
const PACK_LABELS = {
|
|
13
14
|
agents: "dev-agents (backend · frontend · product)",
|
|
14
15
|
};
|
|
@@ -99,6 +100,9 @@ export async function runInit(flags) {
|
|
|
99
100
|
ui.step("Configuring agents");
|
|
100
101
|
for (const id of agents)
|
|
101
102
|
ui.ok(`${agentById(id).label} ${c.muted("— symlinks + MCP")}`);
|
|
103
|
+
// ai-specs/ is gitignored (regenerated by init/update). If a prior setup
|
|
104
|
+
// already committed it, tell the user how to untrack it.
|
|
105
|
+
reportTrackedLocalContent(cwd);
|
|
102
106
|
// 2. Compass index with progress
|
|
103
107
|
if (!flags["no-index"]) {
|
|
104
108
|
ui.step("Indexing your code with Compass");
|
|
@@ -132,4 +136,6 @@ export async function runInit(flags) {
|
|
|
132
136
|
ui.info(`Refresh index: ${ui.code("speclaw index")}`);
|
|
133
137
|
ui.info(`Health check: ${ui.code("speclaw doctor")}`);
|
|
134
138
|
ui.plain();
|
|
139
|
+
ui.info(`${c.muted("ai-specs/ is local (gitignored) — teammates run")} ${ui.code("speclaw init")} ${c.muted("after cloning to regenerate it.")}`);
|
|
140
|
+
ui.plain();
|
|
135
141
|
}
|
|
@@ -10,6 +10,7 @@ import { detectConfiguredAgents } from "../../shared/agents.js";
|
|
|
10
10
|
import { readManifest } from "../../shared/manifest.js";
|
|
11
11
|
import { loadPacks } from "../../modules/tools/packs.js";
|
|
12
12
|
import { detectProjectName } from "./init.js";
|
|
13
|
+
import { reportTrackedLocalContent } from "../lib/untrack.js";
|
|
13
14
|
// The first migration must be tagged at the version that introduces this
|
|
14
15
|
// mechanism (0.1.12): `isNewer` is strict, so an entry tagged at an already-
|
|
15
16
|
// shipped version (e.g. 0.1.11) would never fire for projects already on it.
|
|
@@ -196,6 +197,10 @@ function applyProjectMigrations(cwd, backup) {
|
|
|
196
197
|
console.log(c.cream(prompt));
|
|
197
198
|
ui.plain();
|
|
198
199
|
}
|
|
200
|
+
// This release makes ai-specs/ local (gitignored). A project that committed
|
|
201
|
+
// it before now still tracks it — point out how to untrack (speclaw never
|
|
202
|
+
// touches the git index itself).
|
|
203
|
+
reportTrackedLocalContent(cwd);
|
|
199
204
|
ui.plain();
|
|
200
205
|
ui.ok(`On ${c.cyan(pkgVersion())}. No re-init needed.`);
|
|
201
206
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { ui, c } from "./ui.js";
|
|
2
|
+
import { listTrackedPaths } from "../../shared/git.js";
|
|
3
|
+
/**
|
|
4
|
+
* If `ai-specs/` is still tracked by git, print the exact `git rm -r --cached`
|
|
5
|
+
* command to untrack it. `ai-specs/` is regenerable from the package, so
|
|
6
|
+
* init/update gitignore it — but adding a `.gitignore` entry does not stop git
|
|
7
|
+
* tracking a directory it already tracks, so this is how an already-installed
|
|
8
|
+
* project makes that content local. It only prints — it never modifies the git
|
|
9
|
+
* index — and no-ops silently outside a git repository or when nothing is
|
|
10
|
+
* tracked. The agents' IDE directories (`.claude/`, …) are deliberately left
|
|
11
|
+
* alone, so a user's own skills/commands there stay committable.
|
|
12
|
+
*
|
|
13
|
+
* @param projectPath - Project root to inspect and address.
|
|
14
|
+
*/
|
|
15
|
+
export function reportTrackedLocalContent(projectPath) {
|
|
16
|
+
const tracked = listTrackedPaths(projectPath, ["ai-specs"]);
|
|
17
|
+
if (!tracked.length)
|
|
18
|
+
return;
|
|
19
|
+
ui.step("Make ai-specs/ local-only");
|
|
20
|
+
ui.info("git still tracks ai-specs/ (regenerable). To stop tracking it (it stays on disk):");
|
|
21
|
+
ui.plain();
|
|
22
|
+
console.log(" " + c.cream(`git rm -r --cached ${tracked.join(" ")}`));
|
|
23
|
+
console.log(" " + c.cream('git commit -m "chore: stop tracking speclaw local content"'));
|
|
24
|
+
ui.plain();
|
|
25
|
+
}
|
|
@@ -32,7 +32,9 @@ itself — never delegates it.
|
|
|
32
32
|
## Reports
|
|
33
33
|
|
|
34
34
|
Every change carries a `reports/` folder. `build` writes one report per
|
|
35
|
-
discipline it touched (`backend.md`,
|
|
35
|
+
discipline it touched, named for that discipline — an open set (`backend.md`,
|
|
36
|
+
`frontend.md`, `api.md`, `database.md`, `infra.md`, … — `api.md` required
|
|
37
|
+
whenever the change touches an API surface) recording what was tested
|
|
36
38
|
and the real results — unit, integration, and end-to-end as applicable — with
|
|
37
39
|
the commands run and their output. It is evidence of testing that travels with
|
|
38
40
|
the change; the archive is blocked until at least one discipline report exists.
|
|
@@ -45,9 +45,18 @@ suppressing a linter or deleting a test.
|
|
|
45
45
|
## Reports — evidence travels with the change
|
|
46
46
|
|
|
47
47
|
Every change records its testing under `lawbook/changes/<name>/reports/`, one
|
|
48
|
-
file per discipline it touched
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
file per discipline it touched, named for that discipline. The set is open, not
|
|
49
|
+
fixed: `backend.md`, `frontend.md`, and `api.md` are the common ones, but write
|
|
50
|
+
`database.md`, `infra.md`, `security.md`, `performance.md`, `e2e.md`, etc. when
|
|
51
|
+
the change exercises those concerns. `build` produces them; archiving is blocked
|
|
52
|
+
until the change has at least one discipline report.
|
|
53
|
+
|
|
54
|
+
`api.md` is **mandatory whenever the change touches an API surface** — a new or
|
|
55
|
+
modified endpoint, its contract, its status codes, or its auth/permission or
|
|
56
|
+
ordering guarantees — and a `backend.md` unit report does not substitute for it.
|
|
57
|
+
It documents the method and path, the auth/permissions, the response shape and
|
|
58
|
+
every status code the change governs, any ordering guarantee, and how the
|
|
59
|
+
contract was exercised (test client and/or `curl`) kept isolated from live data.
|
|
51
60
|
|
|
52
61
|
Each report MUST follow a fixed structure, so the evidence is reproducible rather
|
|
53
62
|
than improvised:
|
|
@@ -104,6 +104,10 @@ export function scaffold(projectPath, profile, packNames, agents = [], opts = {}
|
|
|
104
104
|
installPack(projectPath, name, vars, report, managedOpts); // managed
|
|
105
105
|
ensureGitignore(projectPath, ".speclaw/", "speclaw local code Compass (never commit)", report);
|
|
106
106
|
ensureGitignore(projectPath, "*.bak", "speclaw managed-file refresh backups", report);
|
|
107
|
+
// ai-specs/ is regenerable from the package (installWorkflow/installPack copy
|
|
108
|
+
// it out of the module assets) plus the local .speclaw.json manifest — local,
|
|
109
|
+
// per-checkout content, reconstructed by init/update, never committed.
|
|
110
|
+
ensureGitignore(projectPath, "ai-specs/", "speclaw workflow content (regenerated by init/update; never commit)", report);
|
|
107
111
|
for (const id of agents)
|
|
108
112
|
configureAgent(projectPath, id, report); // only the chosen agents
|
|
109
113
|
// Record what was installed so `speclaw update` can re-apply these packs and
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Require one test report per discipline a change touches (an open set — backend, frontend, api, database, infra, security, performance, e2e, …), with the api report mandatory whenever the change touches an API surface.
|
|
3
|
+
alwaysApply: true
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Spec Reports: Discipline Coverage
|
|
7
|
+
|
|
8
|
+
A change's evidence of testing lives under `lawbook/changes/<name>/reports/`, as
|
|
9
|
+
**one file per discipline the change actually touched**, named for that
|
|
10
|
+
discipline (`<discipline>.md`) — never a single lumped report.
|
|
11
|
+
|
|
12
|
+
## 1. The set of disciplines is open — name what the change touched
|
|
13
|
+
|
|
14
|
+
There is no fixed list. Write a report for each area of concern the change
|
|
15
|
+
exercised, and omit the ones it did not. Common disciplines include, but are
|
|
16
|
+
**not limited to**:
|
|
17
|
+
|
|
18
|
+
- **`backend.md`** — domain/service logic, persistence, jobs.
|
|
19
|
+
- **`frontend.md`** — UI flows, components, client state.
|
|
20
|
+
- **`api.md`** — the endpoint contract itself (see §2).
|
|
21
|
+
- **`database.md`** — schema changes, migrations, data integrity.
|
|
22
|
+
- **`infra.md`** — IaC, CI/CD pipelines, deployment/runtime config.
|
|
23
|
+
- **`security.md`** — authn/authz, secrets, attack surface.
|
|
24
|
+
- **`performance.md`** — benchmarks, load, latency budgets.
|
|
25
|
+
- **`e2e.md`** — full cross-service or cross-layer journeys.
|
|
26
|
+
- **`mobile.md`**, **`contract.md`**, **`data.md`**, **`accessibility.md`**, …
|
|
27
|
+
|
|
28
|
+
When a change touches a concern none of these names fit, coin a clear
|
|
29
|
+
`<discipline>.md` for it rather than folding it into an ill-fitting bucket. The
|
|
30
|
+
goal is that the evidence a reviewer needs for each concern is where they expect
|
|
31
|
+
it — not that reports match a checklist.
|
|
32
|
+
|
|
33
|
+
## 2. The API report is mandatory whenever an API surface is touched
|
|
34
|
+
|
|
35
|
+
`api` is called out because it is the discipline most often (wrongly) absorbed
|
|
36
|
+
into `backend` and lost. When a change **adds or modifies an API surface** — a
|
|
37
|
+
new or changed endpoint, its request/response contract, its status codes, or its
|
|
38
|
+
auth/permission or ordering guarantees — you MUST write `api.md`. This is not
|
|
39
|
+
optional:
|
|
40
|
+
|
|
41
|
+
- A `backend.md` unit report does **not** substitute for it — the contract
|
|
42
|
+
(shape, status codes, auth, ordering) is a distinct concern a service-unit
|
|
43
|
+
report does not capture.
|
|
44
|
+
- A `frontend.md` report does **not** substitute for it either.
|
|
45
|
+
|
|
46
|
+
A change that touches no endpoint or contract MAY omit `api.md`.
|
|
47
|
+
|
|
48
|
+
`api.md` MUST document the contract: the method and path, the auth and
|
|
49
|
+
permissions required, the response shape and **every** status code the change
|
|
50
|
+
governs (e.g. `200`/`401`/`403`/`404`), and any ordering or consistency
|
|
51
|
+
guarantee. It MUST record how the contract was exercised — a test client and/or a
|
|
52
|
+
live request such as `curl` — and, per the verification-safety rule, how that
|
|
53
|
+
exercise stayed isolated from any live data store (read-only, ephemeral store, or
|
|
54
|
+
rolled-back transaction — never a write to real data without authorization).
|
|
55
|
+
|
|
56
|
+
## 3. Every report follows the required structure
|
|
57
|
+
|
|
58
|
+
Regardless of discipline, each report keeps the fixed structure so evidence is
|
|
59
|
+
reproducible, not improvised: title + header (discipline · change · date ·
|
|
60
|
+
branch · cwd) → gates-and-results table (each check, exact command, real result
|
|
61
|
+
with pass/fail counts) → tests added/updated → spec-scenario coverage table
|
|
62
|
+
(every `#### Scenario` mapped to how it was verified) → pre-existing/unrelated
|
|
63
|
+
failures (with proof, or "none") → pending manual steps (or "none") → one-line
|
|
64
|
+
verdict. See the `build` skill (Step 5) and
|
|
65
|
+
`docs/standards/testing-standards.md`.
|
|
66
|
+
|
|
67
|
+
When a test kind does not yet apply (e.g. no unit runner), the report says so in
|
|
68
|
+
place of that evidence and records the gates and manual verification that stood
|
|
69
|
+
in.
|
|
70
|
+
|
|
71
|
+
## 4. Reports gate the archive
|
|
72
|
+
|
|
73
|
+
`lawbook_archive` refuses to archive while `reports/` holds no discipline report
|
|
74
|
+
(the `reports/README.md` scaffold does not count). Which disciplines a change
|
|
75
|
+
touched — and therefore which reports are owed, including `api.md` for any
|
|
76
|
+
API-touching change — is the agent's responsibility to judge and satisfy before
|
|
77
|
+
archiving; the engine gate counts files but cannot infer the set of concerns a
|
|
78
|
+
change exercised.
|
|
@@ -21,9 +21,11 @@ steps, branch convention, and testing/documentation requirements.
|
|
|
21
21
|
(see `docs/standards/testing-standards.md`).
|
|
22
22
|
- Perform manual verification of the behavior — **the agent executes this
|
|
23
23
|
itself, never the user.**
|
|
24
|
-
- Produce the discipline reports under `reports/`
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
- Produce the discipline reports under `reports/` — one per discipline the change
|
|
25
|
+
touched, from an open set (`backend.md`, `frontend.md`, `api.md`, `database.md`,
|
|
26
|
+
`infra.md`, … — `api.md` is required whenever the change touches an API surface;
|
|
27
|
+
see the `spec-reports-disciplines` rule) with the unit/integration/e2e results
|
|
28
|
+
for what the feature touched.
|
|
27
29
|
- Update the technical documentation the change touches.
|
|
28
30
|
- Archive the change within the same PR (the `archive` command / `lawbook_archive`
|
|
29
31
|
tool).
|
|
@@ -62,9 +62,21 @@ authorization you obtained).
|
|
|
62
62
|
## Step 5 — Write the discipline reports (mandatory)
|
|
63
63
|
|
|
64
64
|
Record the evidence of testing under `lawbook/changes/<name>/reports/`, one file
|
|
65
|
-
per discipline the change touched
|
|
66
|
-
|
|
67
|
-
|
|
65
|
+
per discipline the change touched, named for that discipline. The set is **open,
|
|
66
|
+
not a fixed list** — `backend.md`, `frontend.md`, and `api.md` are the common
|
|
67
|
+
ones, but write `database.md`, `infra.md`, `security.md`, `performance.md`,
|
|
68
|
+
`e2e.md`, etc. when the change exercises those concerns, and coin a clear
|
|
69
|
+
`<discipline>.md` for anything none of them fit. Omit disciplines the change did
|
|
70
|
+
not touch; the archive is blocked until at least one discipline report exists.
|
|
71
|
+
|
|
72
|
+
**`api.md` is mandatory whenever the change touches an API surface** — a new or
|
|
73
|
+
modified endpoint, its request/response contract, its status codes, or its
|
|
74
|
+
auth/permission or ordering guarantees. A `backend.md` unit report does not
|
|
75
|
+
substitute for it: the contract is a distinct concern. In `api.md` document the
|
|
76
|
+
method and path, the auth/permissions, the response shape and every status code
|
|
77
|
+
the change governs (e.g. `200`/`401`/`403`/`404`), any ordering guarantee, and
|
|
78
|
+
how the contract was exercised (test client and/or `curl`) — kept isolated from
|
|
79
|
+
any live data store per Step 4.
|
|
68
80
|
|
|
69
81
|
Each report MUST follow this structure, in order — the fixed shape is what makes
|
|
70
82
|
the evidence trustworthy and reproducible, rather than left to improvisation:
|
|
@@ -68,8 +68,11 @@ Create under `lawbook/changes/<name>/`:
|
|
|
68
68
|
manual verification executed by the agent; discipline reports produced; docs
|
|
69
69
|
updated; archive within the PR).
|
|
70
70
|
- **reports/** — create the folder with a short `reports/README.md` naming the
|
|
71
|
-
discipline reports
|
|
72
|
-
|
|
71
|
+
discipline reports the change will need — one per discipline it touches, from an
|
|
72
|
+
open set (`backend.md`, `frontend.md`, `api.md`, `database.md`, `infra.md`,
|
|
73
|
+
`security.md`, … — and `api.md` is required when the change touches any API
|
|
74
|
+
surface) that `build` will fill, following the required report structure
|
|
75
|
+
(header · gates table ·
|
|
73
76
|
tests added · spec-scenario coverage · pre-existing failures · pending manual ·
|
|
74
77
|
verdict — see the `build` skill, Step 5). Every change ships this folder;
|
|
75
78
|
archive is blocked until it holds at least one discipline report.
|
|
@@ -26,7 +26,7 @@ mandatory_task_steps:
|
|
|
26
26
|
- "Review and update the affected tests."
|
|
27
27
|
- "Run the quality gates and verify they pass (see docs/standards/testing-standards.md)."
|
|
28
28
|
- "Perform manual verification of the behavior — the agent executes this itself, never the user."
|
|
29
|
-
- "Produce the discipline reports under reports/ (unit/integration/e2e results for what the feature touched
|
|
29
|
+
- "Produce the discipline reports under reports/ — one per discipline touched, from an open set (e.g. backend.md, frontend.md, api.md, database.md, infra.md, security.md; api.md is required whenever the change touches an API surface) — with the unit/integration/e2e results for what the feature touched."
|
|
30
30
|
- "Update the technical documentation touched by the change."
|
|
31
31
|
- "Archive the change within the same PR (lawbook:archive)."
|
|
32
32
|
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
/**
|
|
3
|
+
* True when `projectPath` is inside a git working tree.
|
|
4
|
+
*
|
|
5
|
+
* Best-effort: shells `git rev-parse --is-inside-work-tree` and treats any
|
|
6
|
+
* failure (git not installed, not a repository) as "not a repo" rather than
|
|
7
|
+
* throwing — callers use this only to decide whether to attempt further git
|
|
8
|
+
* queries.
|
|
9
|
+
*
|
|
10
|
+
* @param projectPath - Directory to test.
|
|
11
|
+
* @returns `true` only when git reports the path is inside a work tree.
|
|
12
|
+
*/
|
|
13
|
+
export function isGitRepo(projectPath) {
|
|
14
|
+
const res = spawnSync("git", ["-C", projectPath, "rev-parse", "--is-inside-work-tree"], {
|
|
15
|
+
encoding: "utf8",
|
|
16
|
+
});
|
|
17
|
+
return res.status === 0 && res.stdout.trim() === "true";
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Of `candidates` (project-relative paths), the subset git currently tracks.
|
|
21
|
+
*
|
|
22
|
+
* Adding a path to `.gitignore` does not stop git tracking a file it already
|
|
23
|
+
* tracks; this reports which speclaw paths are still tracked so a command can
|
|
24
|
+
* tell the user how to untrack them. Returns an empty array when `projectPath`
|
|
25
|
+
* is not a git repository (or git is unavailable).
|
|
26
|
+
*
|
|
27
|
+
* @param projectPath - Project root to query.
|
|
28
|
+
* @param candidates - Project-relative paths (files, directories, or symlinks).
|
|
29
|
+
* @returns The candidates for which `git ls-files` reports at least one tracked
|
|
30
|
+
* entry, in the order given.
|
|
31
|
+
*/
|
|
32
|
+
export function listTrackedPaths(projectPath, candidates) {
|
|
33
|
+
if (!isGitRepo(projectPath))
|
|
34
|
+
return [];
|
|
35
|
+
return candidates.filter((rel) => {
|
|
36
|
+
const res = spawnSync("git", ["-C", projectPath, "ls-files", "--", rel], { encoding: "utf8" });
|
|
37
|
+
return res.status === 0 && res.stdout.trim().length > 0;
|
|
38
|
+
});
|
|
39
|
+
}
|