@danielblomma/cortex-mcp 2.0.4 → 2.0.6
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/bin/cortex.mjs +74 -25
- package/package.json +1 -1
- package/scaffold/mcp/package-lock.json +63 -4
- package/scaffold/mcp/package.json +4 -1
- package/scaffold/mcp/src/cli/stage.ts +325 -0
- package/scaffold/mcp/src/core/workflow/artifact-io.ts +156 -0
- package/scaffold/mcp/src/core/workflow/capabilities.ts +100 -0
- package/scaffold/mcp/src/core/workflow/default-workflows.ts +83 -0
- package/scaffold/mcp/src/core/workflow/enforcement.ts +206 -0
- package/scaffold/mcp/src/core/workflow/envelope.ts +220 -0
- package/scaffold/mcp/src/core/workflow/index.ts +8 -0
- package/scaffold/mcp/src/core/workflow/mcp-tools.ts +208 -0
- package/scaffold/mcp/src/core/workflow/run-lifecycle.ts +165 -0
- package/scaffold/mcp/src/core/workflow/schemas.ts +125 -0
- package/scaffold/mcp/src/hooks/pre-tool-use.ts +30 -0
- package/scaffold/mcp/src/server.ts +75 -0
- package/scaffold/mcp/tests/workflow-cli.test.mjs +293 -0
- package/scaffold/mcp/tests/workflow-enforcement.test.mjs +370 -0
- package/scaffold/mcp/tests/workflow-envelope.test.mjs +247 -0
- package/scaffold/mcp/tests/workflow-mcp-tools.test.mjs +293 -0
- package/scaffold/mcp/tests/workflow.test.mjs +283 -0
- package/scaffold/scripts/bootstrap.sh +1 -1
- package/scaffold/scripts/doctor.sh +6 -6
- package/scaffold/scripts/embed.sh +2 -2
- package/scaffold/scripts/load-ryu.sh +3 -3
- package/scaffold/scripts/memory-compile.mjs +1 -1
- package/scaffold/scripts/memory-lint.mjs +1 -1
- package/scaffold/scripts/watch.sh +2 -7
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import os from "node:os";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
parseStageArtifact,
|
|
9
|
+
renderStageArtifact,
|
|
10
|
+
readRunState,
|
|
11
|
+
readStageArtifact,
|
|
12
|
+
} from "../dist/core/workflow/artifact-io.js";
|
|
13
|
+
import {
|
|
14
|
+
createRun,
|
|
15
|
+
advanceStage,
|
|
16
|
+
getRunState,
|
|
17
|
+
} from "../dist/core/workflow/run-lifecycle.js";
|
|
18
|
+
import {
|
|
19
|
+
workflowDefinitionSchema,
|
|
20
|
+
stageArtifactFrontmatterSchema,
|
|
21
|
+
runStateSchema,
|
|
22
|
+
} from "../dist/core/workflow/schemas.js";
|
|
23
|
+
import { SECURE_BUILD_WORKFLOW } from "../dist/core/workflow/default-workflows.js";
|
|
24
|
+
|
|
25
|
+
function makeWorkspace() {
|
|
26
|
+
return fs.mkdtempSync(path.join(os.tmpdir(), "cortex-workflow-"));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const TINY_WORKFLOW = {
|
|
30
|
+
id: "tiny",
|
|
31
|
+
description: "Two-stage workflow used for tests",
|
|
32
|
+
version: 1,
|
|
33
|
+
stages: [
|
|
34
|
+
{
|
|
35
|
+
name: "plan",
|
|
36
|
+
artifact: "plan.md",
|
|
37
|
+
reads: [],
|
|
38
|
+
required_fields: [],
|
|
39
|
+
description: "Produce a plan",
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
name: "review",
|
|
43
|
+
artifact: "review.md",
|
|
44
|
+
reads: ["plan"],
|
|
45
|
+
required_fields: ["approved"],
|
|
46
|
+
description: "Review the plan",
|
|
47
|
+
},
|
|
48
|
+
],
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
test("schemas: SECURE_BUILD_WORKFLOW validates against workflowDefinitionSchema", () => {
|
|
52
|
+
const parsed = workflowDefinitionSchema.parse(SECURE_BUILD_WORKFLOW);
|
|
53
|
+
assert.equal(parsed.id, "secure-build");
|
|
54
|
+
assert.ok(parsed.stages.length > 0);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test("schemas: stage names must be slug-cased", () => {
|
|
58
|
+
assert.throws(() =>
|
|
59
|
+
workflowDefinitionSchema.parse({
|
|
60
|
+
...TINY_WORKFLOW,
|
|
61
|
+
stages: [
|
|
62
|
+
{ ...TINY_WORKFLOW.stages[0], name: "Bad Name" },
|
|
63
|
+
TINY_WORKFLOW.stages[1],
|
|
64
|
+
],
|
|
65
|
+
}),
|
|
66
|
+
);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test("schemas: stage artifact frontmatter requires status + stage", () => {
|
|
70
|
+
assert.throws(() =>
|
|
71
|
+
stageArtifactFrontmatterSchema.parse({
|
|
72
|
+
stage: "plan",
|
|
73
|
+
// status missing
|
|
74
|
+
written_at: new Date().toISOString(),
|
|
75
|
+
}),
|
|
76
|
+
);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test("artifact-io: render + parse round-trips frontmatter", () => {
|
|
80
|
+
const fm = stageArtifactFrontmatterSchema.parse({
|
|
81
|
+
stage: "plan",
|
|
82
|
+
status: "complete",
|
|
83
|
+
references: [],
|
|
84
|
+
written_at: "2026-05-06T19:00:00.000Z",
|
|
85
|
+
});
|
|
86
|
+
const text = renderStageArtifact(fm, "# Plan\n\nDo the thing.");
|
|
87
|
+
const parsed = parseStageArtifact(text);
|
|
88
|
+
assert.equal(parsed.frontmatter.stage, "plan");
|
|
89
|
+
assert.equal(parsed.frontmatter.status, "complete");
|
|
90
|
+
assert.equal(parsed.body, "# Plan\n\nDo the thing.");
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test("artifact-io: parseStageArtifact rejects missing frontmatter", () => {
|
|
94
|
+
assert.throws(() => parseStageArtifact("# No frontmatter here\n"));
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test("artifact-io: parseStageArtifact rejects unterminated frontmatter", () => {
|
|
98
|
+
assert.throws(() =>
|
|
99
|
+
parseStageArtifact("---\nstage: plan\nstatus: complete\n# no close marker\n"),
|
|
100
|
+
);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test("artifact-io: parseStageArtifact preserves passthrough fields", () => {
|
|
104
|
+
const text = `---
|
|
105
|
+
stage: review
|
|
106
|
+
status: complete
|
|
107
|
+
references:
|
|
108
|
+
- plan.md
|
|
109
|
+
written_at: "2026-05-06T19:00:00.000Z"
|
|
110
|
+
approved: true
|
|
111
|
+
blocking_comments: 0
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
# Review
|
|
115
|
+
|
|
116
|
+
Looks good.
|
|
117
|
+
`;
|
|
118
|
+
const parsed = parseStageArtifact(text);
|
|
119
|
+
assert.equal(parsed.frontmatter.stage, "review");
|
|
120
|
+
assert.deepEqual(parsed.frontmatter.references, ["plan.md"]);
|
|
121
|
+
assert.equal(parsed.frontmatter.approved, true);
|
|
122
|
+
assert.equal(parsed.frontmatter.blocking_comments, 0);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test("createRun: writes state.json with all stages pending and current_stage = first", () => {
|
|
126
|
+
const cwd = makeWorkspace();
|
|
127
|
+
const state = createRun({
|
|
128
|
+
cwd,
|
|
129
|
+
taskId: "2026-05-06-fixture",
|
|
130
|
+
workflow: TINY_WORKFLOW,
|
|
131
|
+
taskDescription: "Test run",
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
assert.equal(state.task_id, "2026-05-06-fixture");
|
|
135
|
+
assert.equal(state.current_stage, "plan");
|
|
136
|
+
assert.equal(state.outcome, "in_progress");
|
|
137
|
+
assert.deepEqual(
|
|
138
|
+
state.stages.map((s) => s.status),
|
|
139
|
+
["pending", "pending"],
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
const persisted = readRunState(cwd, "2026-05-06-fixture");
|
|
143
|
+
assert.deepEqual(persisted, state);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test("advanceStage: writes artifact, updates state, advances current_stage", () => {
|
|
147
|
+
const cwd = makeWorkspace();
|
|
148
|
+
const taskId = "2026-05-06-advance";
|
|
149
|
+
createRun({ cwd, taskId, workflow: TINY_WORKFLOW, taskDescription: "Test" });
|
|
150
|
+
|
|
151
|
+
const after = advanceStage({
|
|
152
|
+
cwd,
|
|
153
|
+
taskId,
|
|
154
|
+
workflow: TINY_WORKFLOW,
|
|
155
|
+
stageName: "plan",
|
|
156
|
+
artifactName: "plan.md",
|
|
157
|
+
frontmatter: { stage: "plan", status: "complete", references: [] },
|
|
158
|
+
body: "# Plan\n\n- step 1\n- step 2",
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
assert.equal(after.current_stage, "review");
|
|
162
|
+
assert.equal(after.outcome, "in_progress");
|
|
163
|
+
assert.equal(after.stages[0].status, "complete");
|
|
164
|
+
assert.equal(after.stages[0].artifact, "plan.md");
|
|
165
|
+
assert.equal(after.stages[1].status, "pending");
|
|
166
|
+
|
|
167
|
+
// Artifact lives on disk under .agents/<taskId>/
|
|
168
|
+
const artifactPath = path.join(cwd, ".agents", taskId, "plan.md");
|
|
169
|
+
assert.ok(fs.existsSync(artifactPath));
|
|
170
|
+
const parsed = readStageArtifact(cwd, taskId, "plan.md");
|
|
171
|
+
assert.equal(parsed.frontmatter.stage, "plan");
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
test("advanceStage: marks run complete after final stage", () => {
|
|
175
|
+
const cwd = makeWorkspace();
|
|
176
|
+
const taskId = "2026-05-06-final";
|
|
177
|
+
createRun({ cwd, taskId, workflow: TINY_WORKFLOW, taskDescription: "Test" });
|
|
178
|
+
|
|
179
|
+
advanceStage({
|
|
180
|
+
cwd,
|
|
181
|
+
taskId,
|
|
182
|
+
workflow: TINY_WORKFLOW,
|
|
183
|
+
stageName: "plan",
|
|
184
|
+
artifactName: "plan.md",
|
|
185
|
+
frontmatter: { stage: "plan", status: "complete", references: [] },
|
|
186
|
+
body: "# Plan",
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
const after = advanceStage({
|
|
190
|
+
cwd,
|
|
191
|
+
taskId,
|
|
192
|
+
workflow: TINY_WORKFLOW,
|
|
193
|
+
stageName: "review",
|
|
194
|
+
artifactName: "review.md",
|
|
195
|
+
frontmatter: {
|
|
196
|
+
stage: "review",
|
|
197
|
+
status: "complete",
|
|
198
|
+
references: ["plan.md"],
|
|
199
|
+
approved: true,
|
|
200
|
+
},
|
|
201
|
+
body: "# Review\n\napproved",
|
|
202
|
+
outcome: { approved: true },
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
assert.equal(after.current_stage, null);
|
|
206
|
+
assert.equal(after.outcome, "complete");
|
|
207
|
+
assert.ok(after.completed_at);
|
|
208
|
+
assert.deepEqual(after.stages[1].outcome, { approved: true });
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
test("advanceStage: blocked status surfaces as run outcome", () => {
|
|
212
|
+
const cwd = makeWorkspace();
|
|
213
|
+
const taskId = "2026-05-06-blocked";
|
|
214
|
+
createRun({ cwd, taskId, workflow: TINY_WORKFLOW, taskDescription: "Test" });
|
|
215
|
+
|
|
216
|
+
const after = advanceStage({
|
|
217
|
+
cwd,
|
|
218
|
+
taskId,
|
|
219
|
+
workflow: TINY_WORKFLOW,
|
|
220
|
+
stageName: "plan",
|
|
221
|
+
artifactName: "plan.md",
|
|
222
|
+
frontmatter: { stage: "plan", status: "blocked", references: [] },
|
|
223
|
+
body: "# Plan blocked",
|
|
224
|
+
status: "blocked",
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
assert.equal(after.outcome, "blocked");
|
|
228
|
+
assert.equal(after.current_stage, null);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
test("advanceStage: refuses to advance the wrong stage", () => {
|
|
232
|
+
const cwd = makeWorkspace();
|
|
233
|
+
const taskId = "2026-05-06-wrong";
|
|
234
|
+
createRun({ cwd, taskId, workflow: TINY_WORKFLOW, taskDescription: "Test" });
|
|
235
|
+
|
|
236
|
+
assert.throws(() =>
|
|
237
|
+
advanceStage({
|
|
238
|
+
cwd,
|
|
239
|
+
taskId,
|
|
240
|
+
workflow: TINY_WORKFLOW,
|
|
241
|
+
stageName: "review",
|
|
242
|
+
artifactName: "review.md",
|
|
243
|
+
frontmatter: { stage: "review", status: "complete", references: [] },
|
|
244
|
+
body: "# Out of order",
|
|
245
|
+
}),
|
|
246
|
+
);
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
test("getRunState: returns null for missing tasks", () => {
|
|
250
|
+
const cwd = makeWorkspace();
|
|
251
|
+
assert.equal(getRunState(cwd, "no-such-task"), null);
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
test("readRunState: validates persisted state against schema", () => {
|
|
255
|
+
const cwd = makeWorkspace();
|
|
256
|
+
const taskId = "2026-05-06-corrupt";
|
|
257
|
+
createRun({ cwd, taskId, workflow: TINY_WORKFLOW, taskDescription: "Test" });
|
|
258
|
+
|
|
259
|
+
// Corrupt the file: drop required field.
|
|
260
|
+
const statePath = path.join(cwd, ".agents", taskId, "state.json");
|
|
261
|
+
const raw = JSON.parse(fs.readFileSync(statePath, "utf8"));
|
|
262
|
+
delete raw.workflow_id;
|
|
263
|
+
fs.writeFileSync(statePath, JSON.stringify(raw, null, 2));
|
|
264
|
+
|
|
265
|
+
assert.throws(() => readRunState(cwd, taskId));
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
test("runStateSchema: rejects unknown outcome", () => {
|
|
269
|
+
assert.throws(() =>
|
|
270
|
+
runStateSchema.parse({
|
|
271
|
+
schema_version: 1,
|
|
272
|
+
task_id: "x",
|
|
273
|
+
workflow_id: "tiny",
|
|
274
|
+
workflow_version: 1,
|
|
275
|
+
task_description: "y",
|
|
276
|
+
current_stage: null,
|
|
277
|
+
outcome: "totally-bogus",
|
|
278
|
+
started_at: new Date().toISOString(),
|
|
279
|
+
completed_at: null,
|
|
280
|
+
stages: [{ name: "plan", status: "complete" }],
|
|
281
|
+
}),
|
|
282
|
+
);
|
|
283
|
+
});
|
|
@@ -3,7 +3,7 @@ set -euo pipefail
|
|
|
3
3
|
|
|
4
4
|
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
5
5
|
CONTEXT_DIR="$REPO_ROOT/.context"
|
|
6
|
-
MCP_DIR="$
|
|
6
|
+
MCP_DIR="$CONTEXT_DIR/mcp"
|
|
7
7
|
|
|
8
8
|
PASS=0
|
|
9
9
|
FAIL=0
|
|
@@ -165,15 +165,15 @@ echo ""
|
|
|
165
165
|
echo " MCP Server"
|
|
166
166
|
|
|
167
167
|
if [[ -f "$MCP_DIR/dist/server.js" ]]; then
|
|
168
|
-
pass "mcp/dist/server.js exists"
|
|
168
|
+
pass ".context/mcp/dist/server.js exists"
|
|
169
169
|
else
|
|
170
|
-
fail "mcp/dist/server.js missing — run: cd mcp && npm run build"
|
|
170
|
+
fail ".context/mcp/dist/server.js missing — run: cd .context/mcp && npm run build"
|
|
171
171
|
fi
|
|
172
172
|
|
|
173
173
|
if [[ -d "$MCP_DIR/node_modules" ]]; then
|
|
174
|
-
pass "mcp/node_modules present"
|
|
174
|
+
pass ".context/mcp/node_modules present"
|
|
175
175
|
else
|
|
176
|
-
fail "mcp/node_modules missing — run: cd mcp && npm install"
|
|
176
|
+
fail ".context/mcp/node_modules missing — run: cd .context/mcp && npm install"
|
|
177
177
|
fi
|
|
178
178
|
|
|
179
179
|
# Quick MCP import check
|
|
@@ -181,7 +181,7 @@ if [[ -f "$MCP_DIR/dist/server.js" ]] && [[ -d "$MCP_DIR/node_modules" ]]; then
|
|
|
181
181
|
MCP_CHECK=$(cd "$REPO_ROOT" && timeout 10 node -e '
|
|
182
182
|
const start = Date.now();
|
|
183
183
|
try {
|
|
184
|
-
require("
|
|
184
|
+
require("./.context/mcp/dist/graph.js");
|
|
185
185
|
console.log("ok " + (Date.now() - start));
|
|
186
186
|
} catch(e) {
|
|
187
187
|
console.log("fail " + e.message);
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
set -euo pipefail
|
|
3
3
|
|
|
4
4
|
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
5
|
-
MCP_DIR="$REPO_ROOT/mcp"
|
|
5
|
+
MCP_DIR="$REPO_ROOT/.context/mcp"
|
|
6
6
|
|
|
7
7
|
if ! command -v npm >/dev/null 2>&1; then
|
|
8
8
|
echo "[embed] npm is required but not found on PATH"
|
|
@@ -11,5 +11,5 @@ fi
|
|
|
11
11
|
|
|
12
12
|
mkdir -p "$MCP_DIR/.npm-cache"
|
|
13
13
|
|
|
14
|
-
echo "[embed] generating embeddings via mcp/embed"
|
|
14
|
+
echo "[embed] generating embeddings via .context/mcp/embed"
|
|
15
15
|
NPM_CONFIG_CACHE="$MCP_DIR/.npm-cache" npm --prefix "$MCP_DIR" run embed --silent -- "$@"
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
set -euo pipefail
|
|
3
3
|
|
|
4
4
|
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
5
|
-
MCP_DIR="$REPO_ROOT/mcp"
|
|
5
|
+
MCP_DIR="$REPO_ROOT/.context/mcp"
|
|
6
6
|
|
|
7
7
|
if [[ ! -f "$MCP_DIR/package.json" ]]; then
|
|
8
8
|
echo "[graph-load] missing $MCP_DIR/package.json"
|
|
@@ -10,8 +10,8 @@ if [[ ! -f "$MCP_DIR/package.json" ]]; then
|
|
|
10
10
|
fi
|
|
11
11
|
|
|
12
12
|
if [[ ! -d "$MCP_DIR/node_modules" ]]; then
|
|
13
|
-
echo "[graph-load] node_modules missing in mcp/"
|
|
14
|
-
echo "[graph-load] run: cd mcp && NPM_CONFIG_CACHE=$MCP_DIR/.npm-cache npm install"
|
|
13
|
+
echo "[graph-load] node_modules missing in .context/mcp/"
|
|
14
|
+
echo "[graph-load] run: cd .context/mcp && NPM_CONFIG_CACHE=$MCP_DIR/.npm-cache npm install"
|
|
15
15
|
exit 1
|
|
16
16
|
fi
|
|
17
17
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
|
-
import { parseFrontmatter, parseStringList } from "
|
|
5
|
+
import { parseFrontmatter, parseStringList } from "../.context/mcp/dist/frontmatter.js";
|
|
6
6
|
|
|
7
7
|
const __filename = fileURLToPath(import.meta.url);
|
|
8
8
|
const __dirname = path.dirname(__filename);
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
|
-
import { parseFrontmatter, parseStringList } from "
|
|
5
|
+
import { parseFrontmatter, parseStringList } from "../.context/mcp/dist/frontmatter.js";
|
|
6
6
|
|
|
7
7
|
const __filename = fileURLToPath(import.meta.url);
|
|
8
8
|
const __dirname = path.dirname(__filename);
|
|
@@ -146,11 +146,9 @@ status_digest() {
|
|
|
146
146
|
fi
|
|
147
147
|
|
|
148
148
|
# Fallback for non-git directories.
|
|
149
|
+
# .context/ excludes the relocated .context/mcp/ tree as well.
|
|
149
150
|
find "$REPO_ROOT" -type f \
|
|
150
151
|
! -path "$REPO_ROOT/.context/*" \
|
|
151
|
-
! -path "$REPO_ROOT/mcp/node_modules/*" \
|
|
152
|
-
! -path "$REPO_ROOT/mcp/dist/*" \
|
|
153
|
-
! -path "$REPO_ROOT/mcp/.npm-cache/*" \
|
|
154
152
|
! -path "$REPO_ROOT/scripts/parsers/node_modules/*" \
|
|
155
153
|
! -path "$REPO_ROOT/scripts/parsers/.npm-cache/*" \
|
|
156
154
|
-print \
|
|
@@ -201,16 +199,13 @@ wait_for_change_event() {
|
|
|
201
199
|
inotifywait)
|
|
202
200
|
inotifywait -q -r \
|
|
203
201
|
-e modify,create,delete,move \
|
|
204
|
-
--exclude '(^|/)\\.git(/|$)|(^|/)\\.context(/|$)|(^|/)
|
|
202
|
+
--exclude '(^|/)\\.git(/|$)|(^|/)\\.context(/|$)|(^|/)scripts/parsers/(node_modules|\\.npm-cache)(/|$)' \
|
|
205
203
|
"$REPO_ROOT" >/dev/null 2>&1 || true
|
|
206
204
|
;;
|
|
207
205
|
fswatch)
|
|
208
206
|
fswatch -1 -r \
|
|
209
207
|
--exclude '(^|/)\\.git(/|$)' \
|
|
210
208
|
--exclude '(^|/)\\.context(/|$)' \
|
|
211
|
-
--exclude '(^|/)mcp/node_modules(/|$)' \
|
|
212
|
-
--exclude '(^|/)mcp/dist(/|$)' \
|
|
213
|
-
--exclude '(^|/)mcp/\\.npm-cache(/|$)' \
|
|
214
209
|
--exclude '(^|/)scripts/parsers/node_modules(/|$)' \
|
|
215
210
|
--exclude '(^|/)scripts/parsers/\\.npm-cache(/|$)' \
|
|
216
211
|
"$REPO_ROOT" >/dev/null 2>&1 || true
|