@esneiderbravo/speclaw 0.2.0 → 0.2.1

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 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">&nbsp; 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. If you edited one locally, `update`
193
- reports the overwrite so you can recover your copy from git; pass `--backup` to
194
- also keep a `<file>.bak`. Any `*.bak` is gitignored.
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
+ }
@@ -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,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
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@esneiderbravo/speclaw",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },