@skill-map/cli 0.15.0 → 0.16.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 +16 -0
- package/dist/cli/tutorial/sm-tutorial.md +74 -6
- package/dist/cli.js +133 -42
- package/dist/cli.js.map +1 -1
- package/dist/index.js +8 -5
- package/dist/index.js.map +1 -1
- package/dist/kernel/index.d.ts +23 -7
- package/dist/kernel/index.js +8 -5
- package/dist/kernel/index.js.map +1 -1
- package/dist/ui/{chunk-E2ZFWQW6.js → chunk-UTJNQ3I2.js} +3 -3
- package/dist/ui/index.html +1 -1
- package/dist/ui/{main-JI2DDER5.js → main-Z2AVUR5B.js} +1 -1
- package/package.json +2 -2
- /package/dist/config/defaults/{skill-mapignore → skillmapignore} +0 -0
package/README.md
CHANGED
|
@@ -25,6 +25,22 @@ npx @skill-map/cli --version
|
|
|
25
25
|
|
|
26
26
|
Both `sm` (short, daily use) and `skill-map` (full name, scripts) are registered as binaries after install. The package name is scoped (`@skill-map/cli`) to sit alongside `@skill-map/spec` under the same npm org; the binaries keep the unprefixed names for ergonomics.
|
|
27
27
|
|
|
28
|
+
## Interactive tutorial (recommended starting point)
|
|
29
|
+
|
|
30
|
+
If you use [Claude Code](https://claude.ai/code), `sm tutorial` is the fastest way to learn the CLI and the live UI without committing your real project to anything:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
mkdir try-skill-map && cd try-skill-map
|
|
34
|
+
sm tutorial # writes sm-tutorial.md into the empty dir
|
|
35
|
+
claude # open Claude Code in the same dir
|
|
36
|
+
# Inside Claude:
|
|
37
|
+
ejecutá @sm-tutorial.md
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Claude loads the SKILL.md and runs the demo (~7 min): fixture, `sm init`, live UI, four "reveals" that show the watcher in action, plus the `.skillmapignore` hide-a-file flow. An optional deep-dive (~30-40 min) covers the rest of the CLI surface (`list`, `graph`, `export`, `orphans`, `plugins`, `db ops`).
|
|
41
|
+
|
|
42
|
+
The verb `sm tutorial` writes a single self-contained file; the SKILL.md ships inside this package, so no extra install needed.
|
|
43
|
+
|
|
28
44
|
## Usage
|
|
29
45
|
|
|
30
46
|
```bash
|
|
@@ -349,7 +349,7 @@ Mark `1-version: done`.
|
|
|
349
349
|
|
|
350
350
|
**Context**: `sm init` creates a hidden `.skill-map/` folder in the
|
|
351
351
|
cwd holding the database where skill-map stores what it learns about
|
|
352
|
-
the project. It also drops a `.
|
|
352
|
+
the project. It also drops a `.skillmapignore` in the cwd with
|
|
353
353
|
default exclusions. Mandatory first step.
|
|
354
354
|
|
|
355
355
|
```bash
|
|
@@ -358,10 +358,10 @@ ls -la .skill-map/
|
|
|
358
358
|
```
|
|
359
359
|
|
|
360
360
|
Expected: `.skill-map/skill-map.db` appears (plus config files), and
|
|
361
|
-
a `.
|
|
361
|
+
a `.skillmapignore` shows up at the root.
|
|
362
362
|
|
|
363
363
|
**After init**, you append the tutorial's entries to the
|
|
364
|
-
`.
|
|
364
|
+
`.skillmapignore` that `sm init` just created (do not create a new
|
|
365
365
|
file — append to the existing one with `Edit`). This prevents
|
|
366
366
|
`sm scan` from picking up the tutorial's internal files as graph nodes:
|
|
367
367
|
|
|
@@ -605,8 +605,76 @@ Tell the tester:
|
|
|
605
605
|
> Confirmá / confirm. If a connector is missing, refresh the
|
|
606
606
|
> browser and tell me.
|
|
607
607
|
|
|
608
|
-
|
|
609
|
-
the
|
|
608
|
+
Wait for confirmation. **Do NOT move on to Reveal 4** until the
|
|
609
|
+
connectors are confirmed visible — Reveal 4 reuses the same live UI
|
|
610
|
+
session.
|
|
611
|
+
|
|
612
|
+
#### Reveal 4 — silence a private file via `.skillmapignore`
|
|
613
|
+
|
|
614
|
+
The first three reveals showed the watcher picking up new files and
|
|
615
|
+
edits. Reveal 4 flips the direction: a file the tester DOES NOT want
|
|
616
|
+
in the graph (a draft, a scratch file, a secret) gets hidden by a
|
|
617
|
+
single line in `.skillmapignore`. Same live mechanism — no restart.
|
|
618
|
+
|
|
619
|
+
`sm init` already wrote a starter `.skillmapignore` at the scope
|
|
620
|
+
root. The tester edits that file plus creates one new fixture node:
|
|
621
|
+
|
|
622
|
+
1. Create (`Write`) `notes/private-credentials.md` — kind `note`,
|
|
623
|
+
simulates a file the tester would never want surfacing publicly:
|
|
624
|
+
```markdown
|
|
625
|
+
---
|
|
626
|
+
name: private-credentials
|
|
627
|
+
description: |
|
|
628
|
+
Personal API tokens — exists in the repo but should not show
|
|
629
|
+
up in the skill-map graph. Demonstrates the .skillmapignore
|
|
630
|
+
flow.
|
|
631
|
+
metadata:
|
|
632
|
+
version: "0.0.1"
|
|
633
|
+
---
|
|
634
|
+
|
|
635
|
+
# Private
|
|
636
|
+
|
|
637
|
+
API_TOKEN: example-not-real
|
|
638
|
+
```
|
|
639
|
+
|
|
640
|
+
2. Confirm the file appears in the graph as a sixth node
|
|
641
|
+
(`notes/private-credentials`). The watcher sees it like any
|
|
642
|
+
other `.md` — that's the point of the demo.
|
|
643
|
+
|
|
644
|
+
3. Edit (`Edit`) `.skillmapignore` and append a single new line at
|
|
645
|
+
the end:
|
|
646
|
+
```
|
|
647
|
+
notes/private-*.md
|
|
648
|
+
```
|
|
649
|
+
(Pattern syntax mirrors `.gitignore` — kaelzhang's `ignore`
|
|
650
|
+
under the hood. A literal path like `notes/private-credentials.md`
|
|
651
|
+
would also work; the glob teaches the broader habit.)
|
|
652
|
+
|
|
653
|
+
4. Confirm the node disappears from the graph in the browser, no
|
|
654
|
+
refresh needed. Six nodes back to five.
|
|
655
|
+
|
|
656
|
+
Tell the tester:
|
|
657
|
+
|
|
658
|
+
> Última magia / one last trick: skill-map stops tracking a file
|
|
659
|
+
> the moment it matches a pattern in `.skillmapignore`. No restart
|
|
660
|
+
> needed — same watcher, opposite direction.
|
|
661
|
+
>
|
|
662
|
+
> Two steps:
|
|
663
|
+
>
|
|
664
|
+
> 1. Create `notes/private-credentials.md` with the content I'll
|
|
665
|
+
> paste → a new node appears on the graph (watcher magic again,
|
|
666
|
+
> expected).
|
|
667
|
+
> 2. Open `.skillmapignore` and append `notes/private-*.md` on a
|
|
668
|
+
> new line → the node disappears from the graph.
|
|
669
|
+
>
|
|
670
|
+
> Use this whenever you have drafts, scratch files, or anything
|
|
671
|
+
> you don't want surfacing in the map. Syntax is the same as
|
|
672
|
+
> `.gitignore`: globs, `!pattern` to re-include, `#` for comments.
|
|
673
|
+
>
|
|
674
|
+
> Did the node vanish?
|
|
675
|
+
|
|
676
|
+
Wait for confirmation. Once they confirm, ask them to stop the
|
|
677
|
+
server with **Ctrl+C** in the terminal before continuing.
|
|
610
678
|
|
|
611
679
|
Mark `3-ui-live: done`.
|
|
612
680
|
|
|
@@ -845,7 +913,7 @@ the cwd, start like this (do NOT repeat pre-flight from scratch):
|
|
|
845
913
|
|
|
846
914
|
If they pick "start over", confirm explicitly. Only after
|
|
847
915
|
confirmation, delete the tutorial files in the cwd
|
|
848
|
-
(`tutorial-state.yml`, `findings.md`, `.
|
|
916
|
+
(`tutorial-state.yml`, `findings.md`, `.skillmapignore`, `.claude/`,
|
|
849
917
|
`notes/`, `.skill-map/`, and any `export.*`, `dump.sql`, or
|
|
850
918
|
`sm-tutorial-report.md` that may have been left behind) and start
|
|
851
919
|
everything from pre-flight.
|
package/dist/cli.js
CHANGED
|
@@ -225,7 +225,7 @@ var JOBS_DIRNAME = "jobs";
|
|
|
225
225
|
var PLUGINS_DIRNAME = "plugins";
|
|
226
226
|
var SETTINGS_FILENAME = "settings.json";
|
|
227
227
|
var LOCAL_SETTINGS_FILENAME = "settings.local.json";
|
|
228
|
-
var IGNORE_FILENAME = ".
|
|
228
|
+
var IGNORE_FILENAME = ".skillmapignore";
|
|
229
229
|
var DEFAULT_DB_REL = `${SKILL_MAP_DIR}/${DB_FILENAME}`;
|
|
230
230
|
var GITIGNORE_ENTRIES = [
|
|
231
231
|
`${SKILL_MAP_DIR}/${LOCAL_SETTINGS_FILENAME}`,
|
|
@@ -609,7 +609,7 @@ function loadBundledIgnoreText() {
|
|
|
609
609
|
return loadDefaultsText();
|
|
610
610
|
}
|
|
611
611
|
function readIgnoreFileText(scopeRoot) {
|
|
612
|
-
const path = resolve2(scopeRoot, ".
|
|
612
|
+
const path = resolve2(scopeRoot, ".skillmapignore");
|
|
613
613
|
if (!existsSync2(path)) return void 0;
|
|
614
614
|
try {
|
|
615
615
|
return readFileSync(path, "utf8");
|
|
@@ -626,11 +626,11 @@ function loadDefaultsText() {
|
|
|
626
626
|
function readDefaultsFromDisk() {
|
|
627
627
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
628
628
|
const candidates = [
|
|
629
|
-
resolve2(here, "../../config/defaults/
|
|
629
|
+
resolve2(here, "../../config/defaults/skillmapignore"),
|
|
630
630
|
// src/kernel/scan/ → src/config/defaults/
|
|
631
|
-
resolve2(here, "../config/defaults/
|
|
631
|
+
resolve2(here, "../config/defaults/skillmapignore"),
|
|
632
632
|
// dist/cli.js → dist/config/defaults/ (siblings)
|
|
633
|
-
resolve2(here, "config/defaults/
|
|
633
|
+
resolve2(here, "config/defaults/skillmapignore")
|
|
634
634
|
];
|
|
635
635
|
for (const candidate of candidates) {
|
|
636
636
|
if (existsSync2(candidate)) {
|
|
@@ -2958,7 +2958,7 @@ var AsyncMutex = class {
|
|
|
2958
2958
|
this.#locked = true;
|
|
2959
2959
|
return;
|
|
2960
2960
|
}
|
|
2961
|
-
await new Promise((
|
|
2961
|
+
await new Promise((resolve23) => this.#waiters.push(resolve23));
|
|
2962
2962
|
this.#locked = true;
|
|
2963
2963
|
}
|
|
2964
2964
|
unlock() {
|
|
@@ -7362,7 +7362,7 @@ import { Command as Command8, Option as Option8 } from "clipanion";
|
|
|
7362
7362
|
// package.json
|
|
7363
7363
|
var package_default = {
|
|
7364
7364
|
name: "@skill-map/cli",
|
|
7365
|
-
version: "0.
|
|
7365
|
+
version: "0.16.0",
|
|
7366
7366
|
description: "skill-map reference implementation \u2014 kernel + CLI + adapters.",
|
|
7367
7367
|
license: "MIT",
|
|
7368
7368
|
type: "module",
|
|
@@ -7421,7 +7421,7 @@ var package_default = {
|
|
|
7421
7421
|
},
|
|
7422
7422
|
dependencies: {
|
|
7423
7423
|
"@hono/node-server": "2.0.1",
|
|
7424
|
-
"@skill-map/spec": "0.
|
|
7424
|
+
"@skill-map/spec": "0.16.0",
|
|
7425
7425
|
ajv: "8.18.0",
|
|
7426
7426
|
"ajv-formats": "3.0.1",
|
|
7427
7427
|
chokidar: "5.0.0",
|
|
@@ -8850,11 +8850,14 @@ import { resolve as resolve14, relative as relative4, sep as sep2 } from "path";
|
|
|
8850
8850
|
import chokidar from "chokidar";
|
|
8851
8851
|
function createChokidarWatcher(opts) {
|
|
8852
8852
|
const absRoots = opts.roots.map((r) => resolve14(opts.cwd, r));
|
|
8853
|
-
const
|
|
8854
|
-
const
|
|
8853
|
+
const ignoreFilterOpt = opts.ignoreFilter;
|
|
8854
|
+
const getFilter = ignoreFilterOpt === void 0 ? void 0 : typeof ignoreFilterOpt === "function" ? ignoreFilterOpt : () => ignoreFilterOpt;
|
|
8855
|
+
const ignored = getFilter ? (path) => {
|
|
8856
|
+
const filter = getFilter();
|
|
8857
|
+
if (!filter) return false;
|
|
8855
8858
|
const rel = relativePathFromRoots(path, absRoots);
|
|
8856
8859
|
if (rel === null) return false;
|
|
8857
|
-
return
|
|
8860
|
+
return filter.ignores(rel);
|
|
8858
8861
|
} : void 0;
|
|
8859
8862
|
const watcher = chokidar.watch(absRoots, {
|
|
8860
8863
|
ignoreInitial: true,
|
|
@@ -9101,7 +9104,7 @@ var InitCommand = class extends SmCommand {
|
|
|
9101
9104
|
details: `
|
|
9102
9105
|
Project scope (default): creates ./.skill-map/ with settings.json,
|
|
9103
9106
|
settings.local.json, and skill-map.db. Drops a starter
|
|
9104
|
-
.
|
|
9107
|
+
.skillmapignore at the scope root and appends the DB + local
|
|
9105
9108
|
settings to .gitignore.
|
|
9106
9109
|
|
|
9107
9110
|
Global scope (-g): same scaffolding under ~/.skill-map/. No
|
|
@@ -9123,7 +9126,7 @@ var InitCommand = class extends SmCommand {
|
|
|
9123
9126
|
description: "Skip the first scan after scaffolding."
|
|
9124
9127
|
});
|
|
9125
9128
|
force = Option9.Boolean("--force", false, {
|
|
9126
|
-
description: "Overwrite an existing settings.json / settings.local.json / .
|
|
9129
|
+
description: "Overwrite an existing settings.json / settings.local.json / .skillmapignore."
|
|
9127
9130
|
});
|
|
9128
9131
|
strict = Option9.Boolean("--strict", false, {
|
|
9129
9132
|
description: "Strict mode: fail on any layered-loader warning AND promote frontmatter warnings to errors during the first scan. Same flag as sm scan / sm config."
|
|
@@ -11563,6 +11566,7 @@ async function runEphemeralPath(opts, dbPath, strict, loadPrior, runScanWith) {
|
|
|
11563
11566
|
}
|
|
11564
11567
|
|
|
11565
11568
|
// cli/commands/watch.ts
|
|
11569
|
+
import { resolve as resolve19 } from "path";
|
|
11566
11570
|
import { Command as Command16, Option as Option16 } from "clipanion";
|
|
11567
11571
|
|
|
11568
11572
|
// cli/i18n/watch.texts.ts
|
|
@@ -11586,20 +11590,24 @@ async function runWatchLoop(opts) {
|
|
|
11586
11590
|
const { context } = opts;
|
|
11587
11591
|
const runtimeCtx = defaultRuntimeContext();
|
|
11588
11592
|
const { cwd } = runtimeCtx;
|
|
11593
|
+
const loadEffectiveConfig = () => loadConfig({ scope: "project", strict: opts.strict, ...runtimeCtx }).effective;
|
|
11594
|
+
const buildCurrentIgnoreFilter = (cfgIn) => {
|
|
11595
|
+
const text = readIgnoreFileText(cwd);
|
|
11596
|
+
const filterOpts = {};
|
|
11597
|
+
if (cfgIn.ignore.length > 0) filterOpts.configIgnore = cfgIn.ignore;
|
|
11598
|
+
if (text !== void 0) filterOpts.ignoreFileText = text;
|
|
11599
|
+
return buildIgnoreFilter(filterOpts);
|
|
11600
|
+
};
|
|
11589
11601
|
let cfg;
|
|
11590
11602
|
try {
|
|
11591
|
-
cfg =
|
|
11603
|
+
cfg = loadEffectiveConfig();
|
|
11592
11604
|
} catch (err) {
|
|
11593
11605
|
const message = formatErrorMessage(err);
|
|
11594
11606
|
context.stderr.write(tx(WATCH_TEXTS.configLoadFailure, { message }));
|
|
11595
11607
|
return ExitCode.Error;
|
|
11596
11608
|
}
|
|
11597
|
-
|
|
11598
|
-
|
|
11599
|
-
if (cfg.ignore.length > 0) ignoreFilterOpts.configIgnore = cfg.ignore;
|
|
11600
|
-
if (ignoreFileText !== void 0) ignoreFilterOpts.ignoreFileText = ignoreFileText;
|
|
11601
|
-
const ignoreFilter = buildIgnoreFilter(ignoreFilterOpts);
|
|
11602
|
-
const strict = opts.strict || cfg.scan.strict === true;
|
|
11609
|
+
let ignoreFilter = buildCurrentIgnoreFilter(cfg);
|
|
11610
|
+
let strict = opts.strict || cfg.scan.strict === true;
|
|
11603
11611
|
const debounceMs = cfg.scan.watch.debounceMs;
|
|
11604
11612
|
const dbPath = defaultProjectDbPath(runtimeCtx);
|
|
11605
11613
|
const pluginRuntime = opts.noPlugins ? emptyPluginRuntime() : await loadPluginRuntime({ scope: "project" });
|
|
@@ -11708,7 +11716,11 @@ async function runWatchLoop(opts) {
|
|
|
11708
11716
|
roots: opts.roots,
|
|
11709
11717
|
cwd,
|
|
11710
11718
|
debounceMs,
|
|
11711
|
-
|
|
11719
|
+
// Pass a getter, NOT the filter directly: the meta-file watcher
|
|
11720
|
+
// below mutates `ignoreFilter` after a `.skillmapignore` /
|
|
11721
|
+
// `.skill-map/settings.json` edit, and chokidar's `ignored`
|
|
11722
|
+
// predicate must read the current value on every event.
|
|
11723
|
+
ignoreFilter: () => ignoreFilter,
|
|
11712
11724
|
onBatch: async () => {
|
|
11713
11725
|
const next = await handleBatch();
|
|
11714
11726
|
if (next === "stop") {
|
|
@@ -11720,6 +11732,29 @@ async function runWatchLoop(opts) {
|
|
|
11720
11732
|
context.stderr.write(tx(WATCH_TEXTS.watcherError, { message: err.message }));
|
|
11721
11733
|
}
|
|
11722
11734
|
});
|
|
11735
|
+
const metaWatcher = createChokidarWatcher({
|
|
11736
|
+
roots: [
|
|
11737
|
+
resolve19(cwd, ".skillmapignore"),
|
|
11738
|
+
resolve19(cwd, ".skill-map", "settings.json")
|
|
11739
|
+
],
|
|
11740
|
+
cwd,
|
|
11741
|
+
debounceMs,
|
|
11742
|
+
onBatch: async () => {
|
|
11743
|
+
if (stopRequested) return;
|
|
11744
|
+
try {
|
|
11745
|
+
cfg = loadEffectiveConfig();
|
|
11746
|
+
ignoreFilter = buildCurrentIgnoreFilter(cfg);
|
|
11747
|
+
strict = opts.strict || cfg.scan.strict === true;
|
|
11748
|
+
await handleBatch();
|
|
11749
|
+
} catch (err) {
|
|
11750
|
+
const message = formatErrorMessage(err);
|
|
11751
|
+
context.stderr.write(tx(WATCH_TEXTS.batchFailed, { message }));
|
|
11752
|
+
}
|
|
11753
|
+
},
|
|
11754
|
+
onError: (err) => {
|
|
11755
|
+
context.stderr.write(tx(WATCH_TEXTS.watcherError, { message: err.message }));
|
|
11756
|
+
}
|
|
11757
|
+
});
|
|
11723
11758
|
const onSignal = () => {
|
|
11724
11759
|
if (stopRequested) return;
|
|
11725
11760
|
stopRequested = true;
|
|
@@ -11728,12 +11763,14 @@ async function runWatchLoop(opts) {
|
|
|
11728
11763
|
process.once("SIGINT", onSignal);
|
|
11729
11764
|
process.once("SIGTERM", onSignal);
|
|
11730
11765
|
await watcher.ready;
|
|
11766
|
+
await metaWatcher.ready;
|
|
11731
11767
|
if (!opts.json) {
|
|
11732
11768
|
context.stderr.write(WATCH_TEXTS.ready);
|
|
11733
11769
|
}
|
|
11734
11770
|
await stopped;
|
|
11735
11771
|
process.removeListener("SIGINT", onSignal);
|
|
11736
11772
|
process.removeListener("SIGTERM", onSignal);
|
|
11773
|
+
await metaWatcher.close();
|
|
11737
11774
|
await watcher.close();
|
|
11738
11775
|
if (!opts.json) {
|
|
11739
11776
|
context.stderr.write(tx(WATCH_TEXTS.stopped, { batchCount }));
|
|
@@ -11748,7 +11785,7 @@ var WatchCommand = class extends SmCommand {
|
|
|
11748
11785
|
details: `
|
|
11749
11786
|
Long-running version of 'sm scan --changed'. Subscribes to the
|
|
11750
11787
|
given roots via chokidar, applies the same ignore chain
|
|
11751
|
-
(.
|
|
11788
|
+
(.skillmapignore + config.ignore + bundled defaults), and
|
|
11752
11789
|
triggers an incremental scan after each debounced batch.
|
|
11753
11790
|
|
|
11754
11791
|
Default debounce is 300ms; configure via 'scan.watch.debounceMs'
|
|
@@ -13166,6 +13203,9 @@ function buildKindRegistry(providers) {
|
|
|
13166
13203
|
return registry;
|
|
13167
13204
|
}
|
|
13168
13205
|
|
|
13206
|
+
// server/watcher.ts
|
|
13207
|
+
import { resolve as resolve20 } from "path";
|
|
13208
|
+
|
|
13169
13209
|
// server/events.ts
|
|
13170
13210
|
function buildWatcherStartedEvent(data) {
|
|
13171
13211
|
return {
|
|
@@ -13188,18 +13228,24 @@ function buildWatcherErrorEvent(data) {
|
|
|
13188
13228
|
var WATCH_ROOT = ".";
|
|
13189
13229
|
function createWatcherService(opts) {
|
|
13190
13230
|
let chokidarHandle = null;
|
|
13231
|
+
let metaHandle = null;
|
|
13191
13232
|
let stopped = false;
|
|
13192
13233
|
const start = async () => {
|
|
13193
|
-
const
|
|
13234
|
+
const cwd = opts.runtimeContext.cwd;
|
|
13235
|
+
const loadEffectiveConfig = () => loadConfig({
|
|
13194
13236
|
scope: opts.options.scope,
|
|
13195
|
-
cwd
|
|
13237
|
+
cwd,
|
|
13196
13238
|
homedir: opts.runtimeContext.homedir
|
|
13197
13239
|
}).effective;
|
|
13198
|
-
const
|
|
13199
|
-
|
|
13200
|
-
|
|
13201
|
-
|
|
13202
|
-
|
|
13240
|
+
const buildCurrentIgnoreFilter = (cfgIn) => {
|
|
13241
|
+
const ignoreFileText = readIgnoreFileText(cwd);
|
|
13242
|
+
const filterOpts = {};
|
|
13243
|
+
if (cfgIn.ignore.length > 0) filterOpts.configIgnore = cfgIn.ignore;
|
|
13244
|
+
if (ignoreFileText !== void 0) filterOpts.ignoreFileText = ignoreFileText;
|
|
13245
|
+
return buildIgnoreFilter(filterOpts);
|
|
13246
|
+
};
|
|
13247
|
+
let cfg = loadEffectiveConfig();
|
|
13248
|
+
let ignoreFilter = buildCurrentIgnoreFilter(cfg);
|
|
13203
13249
|
const debounceMs = opts.debounceMsOverride ?? cfg.scan.watch.debounceMs;
|
|
13204
13250
|
const pluginRuntime = opts.options.noPlugins ? emptyPluginRuntime() : await loadPluginRuntime({ scope: opts.options.scope });
|
|
13205
13251
|
for (const warn of pluginRuntime.warnings) {
|
|
@@ -13230,7 +13276,12 @@ function createWatcherService(opts) {
|
|
|
13230
13276
|
roots: [WATCH_ROOT],
|
|
13231
13277
|
cwd: opts.runtimeContext.cwd,
|
|
13232
13278
|
debounceMs,
|
|
13233
|
-
|
|
13279
|
+
// Pass a getter, NOT the filter directly: the meta-file watcher
|
|
13280
|
+
// below mutates `ignoreFilter` after a `.skillmapignore` /
|
|
13281
|
+
// `.skill-map/settings.json` edit, and chokidar's `ignored`
|
|
13282
|
+
// predicate must read the current value on every event. See
|
|
13283
|
+
// `kernel/scan/watcher.ts` for the supported shapes.
|
|
13284
|
+
ignoreFilter: () => ignoreFilter,
|
|
13234
13285
|
onBatch: async () => {
|
|
13235
13286
|
if (stopped) return;
|
|
13236
13287
|
try {
|
|
@@ -13257,6 +13308,42 @@ function createWatcherService(opts) {
|
|
|
13257
13308
|
if ("ready" in chokidarHandle && chokidarHandle.ready instanceof Promise) {
|
|
13258
13309
|
await chokidarHandle.ready;
|
|
13259
13310
|
}
|
|
13311
|
+
metaHandle = createChokidarWatcher({
|
|
13312
|
+
roots: [
|
|
13313
|
+
resolve20(cwd, ".skillmapignore"),
|
|
13314
|
+
resolve20(cwd, ".skill-map", "settings.json")
|
|
13315
|
+
],
|
|
13316
|
+
cwd,
|
|
13317
|
+
debounceMs,
|
|
13318
|
+
// No `ignoreFilter` — these specific paths must always be observed,
|
|
13319
|
+
// regardless of any user pattern.
|
|
13320
|
+
onBatch: async () => {
|
|
13321
|
+
if (stopped) return;
|
|
13322
|
+
try {
|
|
13323
|
+
cfg = loadEffectiveConfig();
|
|
13324
|
+
ignoreFilter = buildCurrentIgnoreFilter(cfg);
|
|
13325
|
+
await runOneBatch();
|
|
13326
|
+
} catch (err) {
|
|
13327
|
+
const message = formatErrorMessage(err);
|
|
13328
|
+
log.warn(
|
|
13329
|
+
tx(SERVER_TEXTS.watcherBatchFailed, {
|
|
13330
|
+
message: sanitizeForTerminal(message)
|
|
13331
|
+
})
|
|
13332
|
+
);
|
|
13333
|
+
}
|
|
13334
|
+
},
|
|
13335
|
+
onError: (err) => {
|
|
13336
|
+
const message = err.message;
|
|
13337
|
+
log.warn(
|
|
13338
|
+
tx(SERVER_TEXTS.watcherError, {
|
|
13339
|
+
message: sanitizeForTerminal(message)
|
|
13340
|
+
})
|
|
13341
|
+
);
|
|
13342
|
+
}
|
|
13343
|
+
});
|
|
13344
|
+
if ("ready" in metaHandle && metaHandle.ready instanceof Promise) {
|
|
13345
|
+
await metaHandle.ready;
|
|
13346
|
+
}
|
|
13260
13347
|
await runInitialBatch({ isStopped: () => stopped, runOneBatch });
|
|
13261
13348
|
opts.broadcaster.broadcast(
|
|
13262
13349
|
buildWatcherStartedEvent({ roots: [WATCH_ROOT], debounceMs })
|
|
@@ -13271,19 +13358,23 @@ function createWatcherService(opts) {
|
|
|
13271
13358
|
const stop = async () => {
|
|
13272
13359
|
if (stopped) return;
|
|
13273
13360
|
stopped = true;
|
|
13274
|
-
|
|
13361
|
+
const closeQuietly = async (handle, label) => {
|
|
13362
|
+
if (!handle) return;
|
|
13275
13363
|
try {
|
|
13276
|
-
await
|
|
13364
|
+
await handle.close();
|
|
13277
13365
|
} catch (err) {
|
|
13278
13366
|
const message = err instanceof Error ? err.message : String(err);
|
|
13279
13367
|
log.warn(
|
|
13280
13368
|
tx(SERVER_TEXTS.watcherCloseFailed, {
|
|
13281
|
-
message: sanitizeForTerminal(message)
|
|
13369
|
+
message: sanitizeForTerminal(`${label}: ${message}`)
|
|
13282
13370
|
})
|
|
13283
13371
|
);
|
|
13284
13372
|
}
|
|
13285
|
-
|
|
13286
|
-
|
|
13373
|
+
};
|
|
13374
|
+
await closeQuietly(metaHandle, "meta-watcher");
|
|
13375
|
+
metaHandle = null;
|
|
13376
|
+
await closeQuietly(chokidarHandle, "primary");
|
|
13377
|
+
chokidarHandle = null;
|
|
13287
13378
|
};
|
|
13288
13379
|
return { start, stop };
|
|
13289
13380
|
}
|
|
@@ -13458,7 +13549,7 @@ function validateWatcherDebounce(value) {
|
|
|
13458
13549
|
|
|
13459
13550
|
// server/paths.ts
|
|
13460
13551
|
import { existsSync as existsSync17, statSync as statSync5 } from "fs";
|
|
13461
|
-
import { dirname as dirname9, isAbsolute as isAbsolute5, join as join14, resolve as
|
|
13552
|
+
import { dirname as dirname9, isAbsolute as isAbsolute5, join as join14, resolve as resolve21 } from "path";
|
|
13462
13553
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
13463
13554
|
var DEFAULT_UI_REL = join14("ui", "dist", "ui", "browser");
|
|
13464
13555
|
var PACKAGE_UI_REL = "ui";
|
|
@@ -13469,7 +13560,7 @@ function resolveDefaultUiDist(ctx) {
|
|
|
13469
13560
|
return walkUpForUi(ctx.cwd);
|
|
13470
13561
|
}
|
|
13471
13562
|
function resolveExplicitUiDist(ctx, raw) {
|
|
13472
|
-
return isAbsolute5(raw) ? raw :
|
|
13563
|
+
return isAbsolute5(raw) ? raw : resolve21(ctx.cwd, raw);
|
|
13473
13564
|
}
|
|
13474
13565
|
function isUiBundleDir(path) {
|
|
13475
13566
|
if (!existsSync17(path)) return false;
|
|
@@ -13503,7 +13594,7 @@ function resolvePackageBundledUiFrom(here) {
|
|
|
13503
13594
|
return null;
|
|
13504
13595
|
}
|
|
13505
13596
|
function walkUpForUi(startDir) {
|
|
13506
|
-
let current =
|
|
13597
|
+
let current = resolve21(startDir);
|
|
13507
13598
|
for (let i = 0; i < 64; i++) {
|
|
13508
13599
|
const candidate = join14(current, DEFAULT_UI_REL);
|
|
13509
13600
|
if (isUiBundleDir(candidate)) return candidate;
|
|
@@ -14467,7 +14558,7 @@ var STUB_COMMANDS = [
|
|
|
14467
14558
|
// cli/commands/tutorial.ts
|
|
14468
14559
|
import { existsSync as existsSync19, readFileSync as readFileSync12 } from "fs";
|
|
14469
14560
|
import { writeFile as writeFile2 } from "fs/promises";
|
|
14470
|
-
import { dirname as dirname10, join as join15, resolve as
|
|
14561
|
+
import { dirname as dirname10, join as join15, resolve as resolve22 } from "path";
|
|
14471
14562
|
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
14472
14563
|
import { Command as Command22, Option as Option22 } from "clipanion";
|
|
14473
14564
|
|
|
@@ -14543,11 +14634,11 @@ function readTutorialFromDisk() {
|
|
|
14543
14634
|
const here = dirname10(fileURLToPath6(import.meta.url));
|
|
14544
14635
|
const candidates = [
|
|
14545
14636
|
// dev: src/cli/commands/ → repo-root .claude/skills/sm-tutorial/SKILL.md
|
|
14546
|
-
|
|
14637
|
+
resolve22(here, "../../../.claude/skills/sm-tutorial/SKILL.md"),
|
|
14547
14638
|
// bundled: dist/cli.js → dist/cli/tutorial/sm-tutorial.md (sibling)
|
|
14548
|
-
|
|
14639
|
+
resolve22(here, "cli/tutorial/sm-tutorial.md"),
|
|
14549
14640
|
// bundled fallback: any-depth → cli/tutorial/sm-tutorial.md
|
|
14550
|
-
|
|
14641
|
+
resolve22(here, "../cli/tutorial/sm-tutorial.md")
|
|
14551
14642
|
];
|
|
14552
14643
|
for (const candidate of candidates) {
|
|
14553
14644
|
if (existsSync19(candidate)) {
|