oris-skills 2.2.3 → 3.0.2
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/.cursor-plugin/plugin.json +2 -3
- package/CHANGELOG.md +24 -6
- package/README.md +28 -60
- package/docs/architecture.md +15 -11
- package/docs/distribution.md +3 -3
- package/docs/maintainer-guide.md +4 -4
- package/docs/user-guide.md +12 -20
- package/package.json +3 -6
- package/references/clean-code-checklist.md +2 -2
- package/references/conventions.md +36 -16
- package/references/doc-policy.md +5 -5
- package/references/research.md +45 -0
- package/references/settings.md +11 -5
- package/references/settings.schema.json +10 -0
- package/scripts/flow/oris-flow-layout.mjs +0 -16
- package/scripts/flow/oris-flow-scan.mjs +32 -317
- package/scripts/flow/oris-gitignore.mjs +1 -5
- package/scripts/install/install-user-skills.mjs +3 -3
- package/scripts/install/uninstall-user-skills.mjs +2 -29
- package/scripts/oris-skills.mjs +0 -47
- package/scripts/tests/run-all-tests.mjs +1 -9
- package/scripts/tests/test-cleanliness.mjs +54 -0
- package/scripts/tests/test-oris-flow-scan.mjs +21 -91
- package/scripts/tests/test-routing-lifecycle.mjs +24 -54
- package/scripts/tests/test-schemas.mjs +17 -31
- package/scripts/tests/test-skill-style.mjs +5 -2
- package/skills/oris-flow/SKILL.md +21 -11
- package/skills/oris-flow/agents/openai.yaml +1 -1
- package/skills/oris-flow/references/architecture.md +6 -3
- package/skills/oris-flow/references/change.md +3 -0
- package/skills/oris-flow/references/commit.md +9 -8
- package/skills/oris-flow/references/criteria.md +9 -5
- package/skills/oris-flow/references/discover.md +2 -2
- package/skills/oris-flow/references/docs.md +5 -1
- package/skills/oris-flow/references/fix.md +10 -5
- package/skills/oris-flow/references/handoff.md +67 -0
- package/skills/oris-flow/references/help.md +3 -6
- package/skills/oris-flow/references/implement.md +7 -4
- package/skills/oris-flow/references/new.md +6 -6
- package/skills/oris-flow/references/plan.md +7 -2
- package/skills/oris-flow/references/pr.md +2 -2
- package/skills/oris-flow/references/setup.md +33 -39
- package/skills/oris-flow/references/triage.md +69 -0
- package/skills/oris-flow/references/verify.md +5 -5
- package/agents/oris-loop-debriefer.md +0 -30
- package/agents/oris-loop-doctor.md +0 -32
- package/agents/oris-loop-executor.md +0 -35
- package/agents/oris-loop-verifier.md +0 -35
- package/references/loop-adapter.schema.json +0 -93
- package/references/loop-contract.md +0 -126
- package/references/loop.schema.json +0 -156
- package/references/repo-map.md +0 -51
- package/references/repo-map.schema.json +0 -213
- package/scripts/flow/oris-flow-clean-runtime.mjs +0 -182
- package/scripts/install/generate-agent-adapters.mjs +0 -81
- package/scripts/loop/oris-loop-bootstrap.mjs +0 -383
- package/scripts/loop/oris-loop-bundle.mjs +0 -22
- package/scripts/loop/oris-loop-chat.mjs +0 -396
- package/scripts/loop/oris-loop-demo.mjs +0 -171
- package/scripts/loop/oris-loop-document.mjs +0 -196
- package/scripts/loop/oris-loop-dry-run.mjs +0 -114
- package/scripts/loop/oris-loop-fixtures.mjs +0 -149
- package/scripts/loop/oris-loop-list.mjs +0 -81
- package/scripts/loop/oris-loop-paths.mjs +0 -232
- package/scripts/loop/oris-loop-run.mjs +0 -301
- package/scripts/loop/oris-loop-stop.mjs +0 -358
- package/scripts/loop/oris-loop-templates.mjs +0 -80
- package/scripts/loop/oris-loop-verify.mjs +0 -205
- package/scripts/tests/test-agent-adapters.mjs +0 -70
- package/scripts/tests/test-oris-1-0-cleanliness.mjs +0 -58
- package/scripts/tests/test-oris-flow-clean-runtime.mjs +0 -75
- package/scripts/tests/test-oris-loop-bootstrap.mjs +0 -94
- package/scripts/tests/test-oris-loop-document.mjs +0 -148
- package/scripts/tests/test-oris-loop-list.mjs +0 -74
- package/scripts/tests/test-oris-loop-run.mjs +0 -71
- package/scripts/tests/test-oris-loop-smoke.mjs +0 -120
- package/scripts/tests/test-oris-loop-stop.mjs +0 -432
- package/skills/oris-flow/references/loop-craft.md +0 -59
- package/skills/oris-flow/references/loop-improve.md +0 -32
- package/skills/oris-flow/references/loop-run.md +0 -47
- package/skills/oris-flow/references/loop.md +0 -56
- package/skills/oris-flow/templates/debriefer.md +0 -19
- package/skills/oris-flow/templates/doctor.md +0 -22
- package/skills/oris-flow/templates/executor.md +0 -25
- package/skills/oris-flow/templates/orchestrator.md +0 -36
- package/skills/oris-flow/templates/verifier.md +0 -24
|
@@ -2,31 +2,15 @@ import os from "node:os";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
|
|
4
4
|
export const ORIS_FLOW_ROOT = ".oris-flow";
|
|
5
|
-
export const ORIS_FLOW_MANIFEST = ".oris-flow/manifest.json";
|
|
6
|
-
export const ORIS_FLOW_MAPS_ROOT = ".oris-flow/maps";
|
|
7
5
|
export const ORIS_FLOW_TASKS_ROOT = ".oris-flow/tasks";
|
|
8
|
-
export const ORIS_FLOW_ADAPTER = ".oris-flow/adapter.json";
|
|
9
|
-
export const ORIS_FLOW_LOOPS_ROOT = ".oris-flow/loops";
|
|
10
|
-
export const ORIS_FLOW_RUNTIME = ".oris-flow/runtime";
|
|
11
6
|
export const ORIS_FLOW_SETTINGS = ".oris-flow/settings.json";
|
|
12
7
|
export const ORIS_HOME = path.join(os.homedir(), ".oris");
|
|
13
8
|
export const ORIS_BUNDLE_ROOT = path.join(ORIS_HOME, "oris-skills");
|
|
14
|
-
export const ORIS_LOOP_SCHEMA = "https://oris.dev/schemas/oris-loop-adapter-v1.json";
|
|
15
|
-
export const ORIS_LOOP_SCHEMA_VERSION = 1;
|
|
16
|
-
export const ORIS_LOOP_DOCUMENT_VERSION = 2;
|
|
17
|
-
export const ORIS_FLOW_MAP_SCHEMA = "https://oris.dev/schemas/oris-flow-map-v1.json";
|
|
18
|
-
export const ORIS_FLOW_MAP_SCHEMA_VERSION = 1;
|
|
19
9
|
|
|
20
10
|
export function toPosixPath(value) {
|
|
21
11
|
return String(value).replace(/\\/g, "/");
|
|
22
12
|
}
|
|
23
13
|
|
|
24
|
-
export function safeSlug(value, fallback = "loop") {
|
|
25
|
-
const raw = path.basename(toPosixPath(value || fallback));
|
|
26
|
-
const slug = raw.replace(/[^A-Za-z0-9_.-]/g, "-").replace(/^-+|-+$/g, "");
|
|
27
|
-
return slug || fallback;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
14
|
export function assertRepositoryRelative(value, label) {
|
|
31
15
|
const normalized = toPosixPath(value ?? "");
|
|
32
16
|
if (!normalized || normalized.startsWith("/") || /^[A-Za-z]:\//.test(normalized)) {
|
|
@@ -1,20 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
/**
|
|
4
|
+
* One-shot repository detector for the setup route. Reports evidence — project
|
|
5
|
+
* type, stacks, commands, notable areas — and writes NOTHING. Durable facts the
|
|
6
|
+
* user confirms belong in AGENTS.md; preferences belong in .oris-flow/settings.json.
|
|
7
|
+
*/
|
|
8
|
+
|
|
4
9
|
import fs from "node:fs";
|
|
5
10
|
import path from "node:path";
|
|
6
11
|
import process from "node:process";
|
|
7
12
|
import { fileURLToPath } from "node:url";
|
|
8
|
-
import {
|
|
9
|
-
ORIS_FLOW_MANIFEST,
|
|
10
|
-
ORIS_FLOW_MAP_SCHEMA,
|
|
11
|
-
ORIS_FLOW_MAP_SCHEMA_VERSION,
|
|
12
|
-
ORIS_FLOW_MAPS_ROOT,
|
|
13
|
-
ORIS_FLOW_TASKS_ROOT,
|
|
14
|
-
relativeToRepository,
|
|
15
|
-
resolveInsideRepository,
|
|
16
|
-
toPosixPath,
|
|
17
|
-
} from "./oris-flow-layout.mjs";
|
|
13
|
+
import { relativeToRepository, toPosixPath } from "./oris-flow-layout.mjs";
|
|
18
14
|
|
|
19
15
|
const ignoredDirectories = new Set([
|
|
20
16
|
".oris-flow",
|
|
@@ -64,74 +60,10 @@ function fail(message) {
|
|
|
64
60
|
process.exit(1);
|
|
65
61
|
}
|
|
66
62
|
|
|
67
|
-
function git(root, args) {
|
|
68
|
-
try {
|
|
69
|
-
const output = childProcess.execFileSync("git", args, { cwd: root, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
|
|
70
|
-
return output.trim();
|
|
71
|
-
} catch {
|
|
72
|
-
return null;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/** Commit SHA the scan anchors to; null outside git (the anchor is then unavailable, never an error). */
|
|
77
|
-
export function gitHeadSha(root) {
|
|
78
|
-
return git(root, ["rev-parse", "HEAD"]);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Files changed since the anchored SHA: committed delta + working-tree changes,
|
|
83
|
-
* deduped. The map's own files (.oris-flow/**) never count — they describe the
|
|
84
|
-
* code, they are not the code.
|
|
85
|
-
*/
|
|
86
|
-
export function gitChangedFiles(root, sinceSha) {
|
|
87
|
-
const diff = git(root, ["diff", "--name-only", `${sinceSha}..HEAD`]);
|
|
88
|
-
const status = git(root, ["status", "--porcelain", "-uall"]);
|
|
89
|
-
if (diff === null && status === null) return null;
|
|
90
|
-
const changed = new Set();
|
|
91
|
-
const add = (file) => {
|
|
92
|
-
const clean = file.replace(/^"|"$/g, "");
|
|
93
|
-
if (clean && !clean.startsWith(".oris-flow/")) changed.add(clean);
|
|
94
|
-
};
|
|
95
|
-
for (const line of (diff ?? "").split(/\r?\n/)) if (line.trim()) add(line.trim());
|
|
96
|
-
for (const line of (status ?? "").split(/\r?\n/)) {
|
|
97
|
-
const file = line.slice(3).trim();
|
|
98
|
-
if (file) add(file.includes(" -> ") ? file.split(" -> ")[1] : file);
|
|
99
|
-
}
|
|
100
|
-
return [...changed].sort();
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* Freshness in one cheap call (the delta-scan anchor): compare the manifest's
|
|
105
|
-
* headSha with the current HEAD. Equal and clean → fresh, zero reads. Moved →
|
|
106
|
-
* the changed file list tells the caller which map areas to refresh in place.
|
|
107
|
-
*/
|
|
108
|
-
export function checkFreshness(repositoryRoot) {
|
|
109
|
-
const root = path.resolve(repositoryRoot);
|
|
110
|
-
const manifest = readJson(path.join(root, ...ORIS_FLOW_MANIFEST.split("/")));
|
|
111
|
-
if (!manifest) return { manifest: false, fresh: false, reason: "no manifest — run a full scan" };
|
|
112
|
-
const headSha = gitHeadSha(root);
|
|
113
|
-
const scannedSha = manifest.headSha ?? null;
|
|
114
|
-
if (!headSha || !scannedSha) {
|
|
115
|
-
return { manifest: true, fresh: false, reason: "no SHA anchor — refresh writes one", headSha, scannedSha, changedFiles: null };
|
|
116
|
-
}
|
|
117
|
-
const changedFiles = gitChangedFiles(root, scannedSha) ?? [];
|
|
118
|
-
const fresh = headSha === scannedSha && changedFiles.length === 0;
|
|
119
|
-
return {
|
|
120
|
-
manifest: true,
|
|
121
|
-
fresh,
|
|
122
|
-
headSha,
|
|
123
|
-
scannedSha,
|
|
124
|
-
changedFiles: fresh ? [] : changedFiles.slice(0, 200),
|
|
125
|
-
sections: Object.keys(manifest.sections ?? {}),
|
|
126
|
-
};
|
|
127
|
-
}
|
|
128
|
-
|
|
129
63
|
function parseArgs(argv) {
|
|
130
64
|
const options = {
|
|
131
65
|
repositoryRoot: process.cwd(),
|
|
132
|
-
write: false,
|
|
133
66
|
json: false,
|
|
134
|
-
area: "",
|
|
135
67
|
};
|
|
136
68
|
const readValue = (index, raw) => {
|
|
137
69
|
if (raw.includes("=")) return [index, raw.slice(raw.indexOf("=") + 1)];
|
|
@@ -142,10 +74,7 @@ function parseArgs(argv) {
|
|
|
142
74
|
const raw = argv[i];
|
|
143
75
|
const key = raw.toLowerCase();
|
|
144
76
|
if (key === "--repository-root" || key.startsWith("--repository-root=")) [i, options.repositoryRoot] = readValue(i, raw);
|
|
145
|
-
else if (key === "--write") options.write = true;
|
|
146
77
|
else if (key === "--json") options.json = true;
|
|
147
|
-
else if (key === "--area" || key.startsWith("--area=")) [i, options.area] = readValue(i, raw);
|
|
148
|
-
else if (key === "--check") options.check = true;
|
|
149
78
|
else fail(`Unknown argument: ${raw}`);
|
|
150
79
|
}
|
|
151
80
|
return options;
|
|
@@ -309,274 +238,60 @@ function deriveStackCommands(stacks, files) {
|
|
|
309
238
|
return commands;
|
|
310
239
|
}
|
|
311
240
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
241
|
+
/** Notable areas the setup interview walks through — evidence pointers, never a stored map. */
|
|
242
|
+
function detectAreas(files) {
|
|
243
|
+
const paths = files.map((file) => file.relative);
|
|
244
|
+
const areas = {};
|
|
315
245
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
pointer: {
|
|
319
|
-
path: pathValue,
|
|
320
|
-
confidence: details.confidence ?? "detected",
|
|
321
|
-
lastScannedAt: details.scannedAt,
|
|
322
|
-
stale: false,
|
|
323
|
-
summary,
|
|
324
|
-
},
|
|
325
|
-
markdown: details.markdown,
|
|
326
|
-
};
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
function buildSections(root, files, stacks, commands, scannedAt) {
|
|
330
|
-
const sections = {};
|
|
331
|
-
const topFiles = files.map((file) => file.relative);
|
|
246
|
+
const docs = paths.filter((file) => /(^|\/)(README|CONTEXT|CHANGELOG|CONTRIBUTING|AGENTS|CLAUDE)\.md$/i.test(file) || file.startsWith("docs/")).slice(0, 20);
|
|
247
|
+
if (docs.length > 0) areas.docs = docs;
|
|
332
248
|
|
|
333
|
-
|
|
334
|
-
const docs = topFiles.filter((file) => /(^|\/)(README|CONTEXT|CHANGELOG|CONTRIBUTING|AGENTS|CLAUDE)\.md$/i.test(file) || file.startsWith("docs/")).slice(0, 20);
|
|
335
|
-
sections.docs = makeSection(".oris-flow/maps/docs.md", "Documentation and task context detected.", {
|
|
336
|
-
scannedAt,
|
|
337
|
-
markdown: [
|
|
338
|
-
"# Docs",
|
|
339
|
-
"",
|
|
340
|
-
"Detected documentation paths:",
|
|
341
|
-
...docs.map((file) => `- ${file}`),
|
|
342
|
-
"",
|
|
343
|
-
].join("\n"),
|
|
344
|
-
});
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
const guidancePaths = topFiles.filter((file) =>
|
|
249
|
+
const agentGuidance = paths.filter((file) =>
|
|
348
250
|
file.startsWith(".cursor/skills/") || file.startsWith(".cursor/rules/")
|
|
349
251
|
|| file.startsWith(".claude/skills/") || /^AGENTS\.md$/i.test(file) || /^CLAUDE\.md$/i.test(file)
|
|
350
252
|
|| /^CONTEXT\.md$/i.test(file) || file.startsWith("docs/adr/")).slice(0, 20);
|
|
351
|
-
if (
|
|
352
|
-
sections.agentGuidance = makeSection(".oris-flow/maps/agent-guidance.md", "Agent guidance detected (AGENTS/CLAUDE/CONTEXT, skills, rules, ADRs).", {
|
|
353
|
-
scannedAt,
|
|
354
|
-
markdown: [
|
|
355
|
-
"# Agent Guidance",
|
|
356
|
-
"",
|
|
357
|
-
"Detected agent-facing guidance:",
|
|
358
|
-
...guidancePaths.map((file) => `- ${file}`),
|
|
359
|
-
"",
|
|
360
|
-
].join("\n"),
|
|
361
|
-
});
|
|
362
|
-
}
|
|
253
|
+
if (agentGuidance.length > 0) areas.agentGuidance = agentGuidance;
|
|
363
254
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
"",
|
|
370
|
-
...stacks.flatMap((stack) => [
|
|
371
|
-
`## ${stack.label}`,
|
|
372
|
-
"",
|
|
373
|
-
...stack.evidence.map((file) => `- ${file}`),
|
|
374
|
-
"",
|
|
375
|
-
]),
|
|
376
|
-
].join("\n"),
|
|
377
|
-
});
|
|
378
|
-
}
|
|
255
|
+
const code = [...new Set(paths
|
|
256
|
+
.filter((file) => /(^|\/)(src|app|apps|packages)\//.test(file))
|
|
257
|
+
.map((file) => file.split("/").slice(0, 2).join("/")))]
|
|
258
|
+
.slice(0, 20);
|
|
259
|
+
if (code.length > 0) areas.code = code;
|
|
379
260
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
scannedAt,
|
|
383
|
-
markdown: [
|
|
384
|
-
"# Commands",
|
|
385
|
-
"",
|
|
386
|
-
...commands.map((command) => `- \`${command.command}\` (${command.evidence || command.confidence}${command.confidence === "inferred" ? ", inferred — confirm before relying on it" : ""})`),
|
|
387
|
-
"",
|
|
388
|
-
].join("\n"),
|
|
389
|
-
});
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
if (hasAny(files, (file) => /(^|\/)(src|app|apps|packages)\//.test(file.relative))) {
|
|
393
|
-
const codeRoots = [...new Set(topFiles
|
|
394
|
-
.filter((file) => /(^|\/)(src|app|apps|packages)\//.test(file))
|
|
395
|
-
.map((file) => file.split("/").slice(0, 2).join("/")))]
|
|
396
|
-
.slice(0, 20);
|
|
397
|
-
sections.code = makeSection(".oris-flow/maps/code.md", "Source code areas detected.", {
|
|
398
|
-
scannedAt,
|
|
399
|
-
markdown: [
|
|
400
|
-
"# Code",
|
|
401
|
-
"",
|
|
402
|
-
"Detected source areas:",
|
|
403
|
-
...codeRoots.map((file) => `- ${file}`),
|
|
404
|
-
"",
|
|
405
|
-
].join("\n"),
|
|
406
|
-
});
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
if (hasAny(files, (file) => /(^|\/)(test|tests|spec|specs|e2e)(\/|$)|\.(test|spec)\.[jt]sx?$/i.test(file.relative))) {
|
|
410
|
-
const tests = topFiles.filter((file) => /(^|\/)(test|tests|spec|specs|e2e)(\/|$)|\.(test|spec)\.[jt]sx?$/i.test(file)).slice(0, 20);
|
|
411
|
-
sections.tests = makeSection(".oris-flow/maps/tests.md", "Test assets detected.", {
|
|
412
|
-
scannedAt,
|
|
413
|
-
markdown: [
|
|
414
|
-
"# Tests",
|
|
415
|
-
"",
|
|
416
|
-
"Detected test paths:",
|
|
417
|
-
...tests.map((file) => `- ${file}`),
|
|
418
|
-
"",
|
|
419
|
-
].join("\n"),
|
|
420
|
-
});
|
|
421
|
-
}
|
|
261
|
+
const tests = paths.filter((file) => /(^|\/)(test|tests|spec|specs|e2e)(\/|$)|\.(test|spec)\.[jt]sx?$/i.test(file)).slice(0, 20);
|
|
262
|
+
if (tests.length > 0) areas.tests = tests;
|
|
422
263
|
|
|
423
|
-
return
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
function resolveTasksRoot(root, existingManifest) {
|
|
427
|
-
const configured = existingManifest?.setup?.tasksRoot;
|
|
428
|
-
if (typeof configured === "string" && configured.length > 0) return configured;
|
|
429
|
-
const legacy = path.join(root, "docs", "tasks");
|
|
430
|
-
if (fs.existsSync(legacy) && fs.readdirSync(legacy).length > 0) return "docs/tasks";
|
|
431
|
-
return ORIS_FLOW_TASKS_ROOT;
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
/**
|
|
435
|
-
* Refresh keeps user truth: confirmed decisions, tasksRoot, version control,
|
|
436
|
-
* confirmed commands, and confirmed sections (whose map files are also left
|
|
437
|
-
* untouched). A confirmed section the scan no longer sees is marked stale,
|
|
438
|
-
* never deleted.
|
|
439
|
-
*/
|
|
440
|
-
function mergeWithExisting(scan, existingManifest) {
|
|
441
|
-
if (!existingManifest || typeof existingManifest !== "object") return scan;
|
|
442
|
-
const merged = scan;
|
|
443
|
-
merged.manifest.generatedAt = existingManifest.generatedAt ?? merged.manifest.generatedAt;
|
|
444
|
-
if (Array.isArray(existingManifest.setup?.confirmedDecisions)) {
|
|
445
|
-
merged.manifest.setup.confirmedDecisions = existingManifest.setup.confirmedDecisions;
|
|
446
|
-
}
|
|
447
|
-
if (existingManifest.setup?.versionControl?.orisFlowMap) {
|
|
448
|
-
merged.manifest.setup.versionControl.orisFlowMap = existingManifest.setup.versionControl.orisFlowMap;
|
|
449
|
-
}
|
|
450
|
-
if (existingManifest.projectType?.confidence === "confirmed") {
|
|
451
|
-
merged.manifest.projectType = existingManifest.projectType;
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
const confirmedCommands = (existingManifest.commands ?? []).filter((command) => command?.confidence === "confirmed");
|
|
455
|
-
if (confirmedCommands.length > 0) {
|
|
456
|
-
const scanned = merged.manifest.commands.filter((command) =>
|
|
457
|
-
!confirmedCommands.some((confirmed) => confirmed.name === command.name && confirmed.cwd === command.cwd));
|
|
458
|
-
merged.manifest.commands = [...confirmedCommands, ...scanned];
|
|
459
|
-
merged.commands = merged.manifest.commands;
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
merged.preservedSections = new Set();
|
|
463
|
-
for (const [key, pointer] of Object.entries(existingManifest.sections ?? {})) {
|
|
464
|
-
if (pointer?.confidence !== "confirmed") continue;
|
|
465
|
-
const rescanned = merged.manifest.sections[key];
|
|
466
|
-
merged.manifest.sections[key] = {
|
|
467
|
-
...pointer,
|
|
468
|
-
stale: rescanned ? pointer.stale === true : true,
|
|
469
|
-
};
|
|
470
|
-
merged.preservedSections.add(key);
|
|
471
|
-
if (merged.sections[key]) delete merged.sections[key];
|
|
472
|
-
}
|
|
473
|
-
return merged;
|
|
264
|
+
return areas;
|
|
474
265
|
}
|
|
475
266
|
|
|
476
267
|
export function scanRepository(repositoryRoot, options = {}) {
|
|
477
268
|
const root = path.resolve(repositoryRoot);
|
|
478
|
-
const scannedAt = new Date().toISOString();
|
|
479
|
-
const headSha = gitHeadSha(root);
|
|
480
|
-
const existingManifest = readJson(path.join(root, ...ORIS_FLOW_MANIFEST.split("/")));
|
|
481
269
|
const { files, truncated } = walk(root, options);
|
|
482
270
|
const stacks = detectStacks(files);
|
|
483
|
-
|
|
484
|
-
const commands = [...findPackageCommands(files), ...deriveStackCommands(stacks, files)];
|
|
485
|
-
const sections = buildSections(root, files, stacks, commands, scannedAt);
|
|
486
|
-
const sectionPointers = Object.fromEntries(
|
|
487
|
-
Object.entries(sections).map(([key, value]) => [key, value.pointer]),
|
|
488
|
-
);
|
|
489
|
-
const manifest = {
|
|
490
|
-
$schema: ORIS_FLOW_MAP_SCHEMA,
|
|
491
|
-
schemaVersion: ORIS_FLOW_MAP_SCHEMA_VERSION,
|
|
492
|
-
repository: path.basename(root),
|
|
493
|
-
generatedAt: scannedAt,
|
|
494
|
-
updatedAt: scannedAt,
|
|
495
|
-
headSha,
|
|
496
|
-
projectType,
|
|
497
|
-
truncated,
|
|
498
|
-
setup: {
|
|
499
|
-
mode: options.mode ?? (existingManifest ? "refresh" : "create"),
|
|
500
|
-
scanDepth: options.scanDepth ?? "balanced",
|
|
501
|
-
tasksRoot: options.tasksRoot ?? resolveTasksRoot(root, existingManifest),
|
|
502
|
-
confirmedDecisions: [],
|
|
503
|
-
versionControl: {
|
|
504
|
-
orisFlowMap: options.versionControl ?? "commit",
|
|
505
|
-
},
|
|
506
|
-
},
|
|
507
|
-
stacks,
|
|
508
|
-
commands,
|
|
509
|
-
sections: sectionPointers,
|
|
510
|
-
};
|
|
511
|
-
const scan = {
|
|
271
|
+
return {
|
|
512
272
|
repositoryRoot: root,
|
|
513
|
-
scannedAt,
|
|
273
|
+
scannedAt: new Date().toISOString(),
|
|
514
274
|
fileCount: files.length,
|
|
515
275
|
truncated,
|
|
516
|
-
projectType,
|
|
276
|
+
projectType: detectProjectType(files, stacks),
|
|
517
277
|
stacks,
|
|
518
|
-
commands,
|
|
519
|
-
|
|
520
|
-
manifest,
|
|
521
|
-
preservedSections: new Set(),
|
|
522
|
-
};
|
|
523
|
-
return mergeWithExisting(scan, existingManifest);
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
export function writeScanResult(scan, options = {}) {
|
|
527
|
-
const root = scan.repositoryRoot;
|
|
528
|
-
const area = options.area ?? "";
|
|
529
|
-
const manifestPath = resolveInsideRepository(root, ORIS_FLOW_MANIFEST, "Oris Flow manifest");
|
|
530
|
-
fs.mkdirSync(path.dirname(manifestPath), { recursive: true });
|
|
531
|
-
fs.writeFileSync(manifestPath, `${JSON.stringify(scan.manifest, null, 2)}\n`, "utf8");
|
|
532
|
-
|
|
533
|
-
const written = [];
|
|
534
|
-
for (const [key, section] of Object.entries(scan.sections)) {
|
|
535
|
-
if (area && key !== area) continue;
|
|
536
|
-
const sectionPath = resolveInsideRepository(root, section.pointer.path, "Oris Flow section");
|
|
537
|
-
fs.mkdirSync(path.dirname(sectionPath), { recursive: true });
|
|
538
|
-
fs.writeFileSync(sectionPath, section.markdown, "utf8");
|
|
539
|
-
written.push(section.pointer.path);
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
const readmePath = resolveInsideRepository(root, `${ORIS_FLOW_MAPS_ROOT}/README.md`, "Oris Flow map README");
|
|
543
|
-
if (!fs.existsSync(readmePath)) {
|
|
544
|
-
fs.mkdirSync(path.dirname(readmePath), { recursive: true });
|
|
545
|
-
fs.writeFileSync(readmePath, "# Oris Flow Maps\n\nGenerated by `oris-flow-scan.mjs`. Sections are present only when repository evidence supports them.\n", "utf8");
|
|
546
|
-
}
|
|
547
|
-
return {
|
|
548
|
-
manifestPath: relativeToRepository(root, manifestPath),
|
|
549
|
-
sectionPaths: written,
|
|
550
|
-
preservedSections: [...(scan.preservedSections ?? [])],
|
|
278
|
+
commands: [...findPackageCommands(files), ...deriveStackCommands(stacks, files)],
|
|
279
|
+
areas: detectAreas(files),
|
|
551
280
|
};
|
|
552
281
|
}
|
|
553
282
|
|
|
554
283
|
if (process.argv[1] && path.resolve(process.argv[1]) === path.resolve(fileURLToPath(import.meta.url))) {
|
|
555
284
|
const options = parseArgs(process.argv.slice(2));
|
|
556
|
-
if (options.check) {
|
|
557
|
-
console.log(JSON.stringify(checkFreshness(options.repositoryRoot), null, 2));
|
|
558
|
-
process.exit(0);
|
|
559
|
-
}
|
|
560
285
|
const scan = scanRepository(options.repositoryRoot, options);
|
|
561
|
-
const
|
|
562
|
-
|
|
563
|
-
repositoryRoot: toPosixPath(scan.repositoryRoot),
|
|
564
|
-
fileCount: scan.fileCount,
|
|
565
|
-
truncated: scan.truncated,
|
|
566
|
-
projectType: scan.projectType,
|
|
567
|
-
tasksRoot: scan.manifest.setup.tasksRoot,
|
|
568
|
-
stacks: scan.stacks,
|
|
569
|
-
commands: scan.commands,
|
|
570
|
-
sections: scan.manifest.sections,
|
|
571
|
-
written,
|
|
572
|
-
};
|
|
573
|
-
if (options.json || options.write) {
|
|
286
|
+
const payload = { ...scan, repositoryRoot: toPosixPath(scan.repositoryRoot) };
|
|
287
|
+
if (options.json) {
|
|
574
288
|
console.log(JSON.stringify(payload, null, 2));
|
|
575
289
|
} else {
|
|
576
290
|
console.log(`Oris Flow scan: ${payload.repositoryRoot}`);
|
|
577
291
|
console.log(`Files scanned: ${payload.fileCount}${payload.truncated ? " (truncated)" : ""}`);
|
|
578
292
|
console.log(`Project type: ${payload.projectType.label}`);
|
|
579
293
|
console.log(`Stacks: ${payload.stacks.map((stack) => stack.label).join(", ") || "none"}`);
|
|
580
|
-
console.log(`
|
|
294
|
+
console.log(`Commands: ${payload.commands.map((command) => command.command).join(", ") || "none"}`);
|
|
295
|
+
console.log(`Areas: ${Object.keys(payload.areas).join(", ") || "none"}`);
|
|
581
296
|
}
|
|
582
297
|
}
|
|
@@ -2,13 +2,9 @@ import fs from "node:fs";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
|
|
4
4
|
export const ORIS_FLOW_GITIGNORE_ENTRIES = [
|
|
5
|
-
".oris-flow/
|
|
6
|
-
".oris-flow/maps/",
|
|
5
|
+
".oris-flow/",
|
|
7
6
|
];
|
|
8
7
|
|
|
9
|
-
export const ORIS_FLOW_RUNTIME_GITIGNORE_ENTRY = ".oris-flow/runtime/";
|
|
10
|
-
export const ORIS_LOOP_GITIGNORE_ENTRY = ORIS_FLOW_RUNTIME_GITIGNORE_ENTRY;
|
|
11
|
-
|
|
12
8
|
function normalizeGitignoreLines(text) {
|
|
13
9
|
return text.replace(/\r\n/g, "\n").split("\n");
|
|
14
10
|
}
|
|
@@ -250,7 +250,7 @@ Tell any agent that can read files:
|
|
|
250
250
|
> Read \`${bundledSkill}\` and follow it exactly.
|
|
251
251
|
|
|
252
252
|
That file routes every Oris flow (setup, discover, criteria, plan, implement, fix,
|
|
253
|
-
verify, change,
|
|
253
|
+
verify, change, architecture, docs, help) in the same chat.
|
|
254
254
|
`, "utf8");
|
|
255
255
|
}
|
|
256
256
|
|
|
@@ -291,7 +291,7 @@ function main() {
|
|
|
291
291
|
|
|
292
292
|
fs.rmSync(options.bundleRoot, { recursive: true, force: true });
|
|
293
293
|
fs.mkdirSync(options.bundleRoot, { recursive: true });
|
|
294
|
-
for (const item of [".cursor-plugin", "skills", "references", "
|
|
294
|
+
for (const item of [".cursor-plugin", "skills", "references", "scripts", "docs", "package.json", "README.md", "LICENSE"]) {
|
|
295
295
|
const source = path.join(repoRoot, item);
|
|
296
296
|
if (fs.existsSync(source)) copy(source, path.join(options.bundleRoot, item));
|
|
297
297
|
}
|
|
@@ -323,7 +323,7 @@ function main() {
|
|
|
323
323
|
installedAt: new Date().toISOString(),
|
|
324
324
|
});
|
|
325
325
|
log(`Done for agents: ${agents.join(", ")}. Reload your agent environment.`);
|
|
326
|
-
log("Next: type /oris-flow in your agent chat
|
|
326
|
+
log("Next: type /oris-flow in your agent chat.");
|
|
327
327
|
}
|
|
328
328
|
|
|
329
329
|
try {
|
|
@@ -24,7 +24,6 @@ function parseArgs(argv) {
|
|
|
24
24
|
const options = {
|
|
25
25
|
dryRun: false,
|
|
26
26
|
bundleRoot: path.join(home, ".oris", "oris-skills"),
|
|
27
|
-
settingsPath: path.join(home, ".oris", "settings.json"),
|
|
28
27
|
cursorRoot: path.join(home, ".cursor"),
|
|
29
28
|
claudeRoot: path.join(home, ".claude"),
|
|
30
29
|
codexRoot: path.join(home, ".codex"),
|
|
@@ -45,7 +44,6 @@ function parseArgs(argv) {
|
|
|
45
44
|
};
|
|
46
45
|
if (key === "--dry-run") options.dryRun = true;
|
|
47
46
|
else if (key === "--bundle-root" || key.startsWith("--bundle-root=")) options.bundleRoot = value();
|
|
48
|
-
else if (key === "--settings" || key.startsWith("--settings=")) options.settingsPath = value();
|
|
49
47
|
else if (key === "--cursor-root" || key.startsWith("--cursor-root=")) options.cursorRoot = value();
|
|
50
48
|
else if (key === "--claude-root" || key.startsWith("--claude-root=")) options.claudeRoot = value();
|
|
51
49
|
else if (key === "--codex-root" || key.startsWith("--codex-root=")) options.codexRoot = value();
|
|
@@ -92,15 +90,6 @@ function managedCodexPrompts(codexRoot) {
|
|
|
92
90
|
.map((entry) => path.join(promptsRoot, entry.name));
|
|
93
91
|
}
|
|
94
92
|
|
|
95
|
-
/** Loop agent adapters that pre-2.2.1 installers wrote into <agent home>/agents/. */
|
|
96
|
-
function managedAgentAdapters(agentRoot, extension) {
|
|
97
|
-
const agentsRoot = path.join(agentRoot, "agents");
|
|
98
|
-
if (!fs.existsSync(agentsRoot)) return [];
|
|
99
|
-
return fs.readdirSync(agentsRoot, { withFileTypes: true })
|
|
100
|
-
.filter((entry) => entry.isFile() && entry.name.toLowerCase().startsWith("oris-") && entry.name.endsWith(extension))
|
|
101
|
-
.map((entry) => path.join(agentsRoot, entry.name));
|
|
102
|
-
}
|
|
103
|
-
|
|
104
93
|
function managedCopilotPrompts(copilotRoot) {
|
|
105
94
|
const promptsRoot = path.join(copilotRoot, "prompts");
|
|
106
95
|
if (!fs.existsSync(promptsRoot)) return [];
|
|
@@ -109,18 +98,6 @@ function managedCopilotPrompts(copilotRoot) {
|
|
|
109
98
|
.map((entry) => path.join(promptsRoot, entry.name));
|
|
110
99
|
}
|
|
111
100
|
|
|
112
|
-
function legacyOrisPaths(options) {
|
|
113
|
-
return [
|
|
114
|
-
path.join(options.cursorRoot, "oris-skills"),
|
|
115
|
-
path.join(options.cursorRoot, "oris-flow"),
|
|
116
|
-
path.join(options.cursorRoot, "oris-loop"),
|
|
117
|
-
path.join(options.cursorRoot, "oris-skills.settings.json"),
|
|
118
|
-
path.join(options.cursorRoot, ".cursor-plugin-install-backups"),
|
|
119
|
-
path.join(options.cursorRoot, "plugins", "oris-skills"),
|
|
120
|
-
options.settingsPath,
|
|
121
|
-
];
|
|
122
|
-
}
|
|
123
|
-
|
|
124
101
|
function plannedRemovals(options) {
|
|
125
102
|
const paths = new Set([
|
|
126
103
|
options.bundleRoot,
|
|
@@ -128,11 +105,7 @@ function plannedRemovals(options) {
|
|
|
128
105
|
...managedSkillDirs(path.join(options.claudeRoot, "skills")),
|
|
129
106
|
...managedCodexPrompts(options.codexRoot),
|
|
130
107
|
...managedCopilotPrompts(options.copilotRoot),
|
|
131
|
-
...managedAgentAdapters(options.cursorRoot, ".md"),
|
|
132
|
-
...managedAgentAdapters(options.claudeRoot, ".md"),
|
|
133
|
-
...managedAgentAdapters(options.codexRoot, ".toml"),
|
|
134
108
|
path.join(path.dirname(options.bundleRoot), "oris-flow.md"),
|
|
135
|
-
...legacyOrisPaths(options),
|
|
136
109
|
]);
|
|
137
110
|
return [...paths].map((target) => path.resolve(target)).filter(safeOrisPath).sort();
|
|
138
111
|
}
|
|
@@ -153,7 +126,7 @@ function removeOrisMcpServers(userMcpPath, dryRun) {
|
|
|
153
126
|
|
|
154
127
|
export function uninstallUserSkills(rawArgs = []) {
|
|
155
128
|
const options = parseArgs(rawArgs);
|
|
156
|
-
for (const target of [options.bundleRoot, options.
|
|
129
|
+
for (const target of [options.bundleRoot, options.cursorRoot, options.claudeRoot, options.codexRoot, options.copilotRoot]) {
|
|
157
130
|
assertInsideHome(target);
|
|
158
131
|
}
|
|
159
132
|
|
|
@@ -161,7 +134,7 @@ export function uninstallUserSkills(rawArgs = []) {
|
|
|
161
134
|
log(options.dryRun ? "Dry run uninstall plan:" : "Uninstalling Oris-owned assets:");
|
|
162
135
|
for (const target of removals) log(`${options.dryRun ? "Would remove" : "Remove"} ${target}`);
|
|
163
136
|
|
|
164
|
-
//
|
|
137
|
+
// The installer seeds the Oris MCP server into Cursor's user config; remove it with the rest.
|
|
165
138
|
const mcpResult = removeOrisMcpServers(path.join(options.cursorRoot, "mcp.json"), options.dryRun);
|
|
166
139
|
if (mcpResult.changed) log(`${options.dryRun ? "Would update" : "Updated"} ${mcpResult.path}`);
|
|
167
140
|
|
package/scripts/oris-skills.mjs
CHANGED
|
@@ -4,7 +4,6 @@ import childProcess from "node:child_process";
|
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import process from "node:process";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
|
-
import { bootstrapLoop } from "./loop/oris-loop-bootstrap.mjs";
|
|
8
7
|
|
|
9
8
|
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
10
9
|
|
|
@@ -19,15 +18,6 @@ function runNode(script, args, options = {}) {
|
|
|
19
18
|
process.exit(status);
|
|
20
19
|
}
|
|
21
20
|
|
|
22
|
-
function runGitRoot() {
|
|
23
|
-
const result = childProcess.spawnSync("git", ["rev-parse", "--show-toplevel"], {
|
|
24
|
-
cwd: process.cwd(),
|
|
25
|
-
shell: process.platform === "win32",
|
|
26
|
-
encoding: "utf8",
|
|
27
|
-
});
|
|
28
|
-
return result.status === 0 && result.stdout.trim() ? path.resolve(result.stdout.trim()) : process.cwd();
|
|
29
|
-
}
|
|
30
|
-
|
|
31
21
|
function printHelp() {
|
|
32
22
|
console.log(`Oris Skills
|
|
33
23
|
|
|
@@ -36,24 +26,10 @@ Install for your agents (Cursor, Claude Code, Codex):
|
|
|
36
26
|
oris-skills uninstall [--dry-run]
|
|
37
27
|
oris-skills reinstall [--dry-run]
|
|
38
28
|
|
|
39
|
-
Learn and test loops:
|
|
40
|
-
oris-skills loop demo create a safe tutorial loop
|
|
41
|
-
oris-skills loop dry-run --loop <slug> preview the next pass, run nothing
|
|
42
|
-
oris-skills loop verify --temp self-check the loop runtime
|
|
43
|
-
|
|
44
|
-
Run and manage loops:
|
|
45
|
-
oris-skills loop list [--json]
|
|
46
|
-
oris-skills loop bootstrap [--platforms cursor,claude]
|
|
47
|
-
oris-skills loop chat --action start --loop <slug>
|
|
48
|
-
oris-skills loop chat --action status|set-state|repair|stop [...]
|
|
49
|
-
oris-skills loop run --loop <slug> --agent codex|claude headless runner
|
|
50
|
-
|
|
51
29
|
Repository tools:
|
|
52
|
-
oris-skills doctor
|
|
53
30
|
oris-skills validate
|
|
54
31
|
oris-skills flow scan [...]
|
|
55
32
|
oris-skills flow version-control [...]
|
|
56
|
-
oris-skills flow clean-runtime [...]
|
|
57
33
|
`);
|
|
58
34
|
}
|
|
59
35
|
|
|
@@ -79,37 +55,14 @@ if (command === "reinstall") {
|
|
|
79
55
|
runNode("install/install-user-skills.mjs", ["--force", ...commandArgs]);
|
|
80
56
|
}
|
|
81
57
|
|
|
82
|
-
if (command === "doctor") {
|
|
83
|
-
const root = runGitRoot();
|
|
84
|
-
const result = bootstrapLoop(root, { dryRun: true });
|
|
85
|
-
console.log(JSON.stringify({
|
|
86
|
-
repositoryRoot: root,
|
|
87
|
-
ready: result.ready,
|
|
88
|
-
missing: result.before.missing,
|
|
89
|
-
plannedActions: result.actions,
|
|
90
|
-
}, null, 2));
|
|
91
|
-
process.exit(result.ready ? 0 : 1);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
58
|
if (command === "validate") {
|
|
95
59
|
// Kept as an alias of the full test suite for older docs and muscle memory.
|
|
96
60
|
runNode("tests/run-all-tests.mjs", []);
|
|
97
61
|
}
|
|
98
62
|
|
|
99
|
-
if (command === "loop") {
|
|
100
|
-
if (subcommand === "list") runNode("loop/oris-loop-list.mjs", rest);
|
|
101
|
-
if (subcommand === "bootstrap") runNode("loop/oris-loop-bootstrap.mjs", rest);
|
|
102
|
-
if (subcommand === "chat") runNode("loop/oris-loop-chat.mjs", rest);
|
|
103
|
-
if (subcommand === "verify") runNode("loop/oris-loop-verify.mjs", rest);
|
|
104
|
-
if (subcommand === "demo") runNode("loop/oris-loop-demo.mjs", rest);
|
|
105
|
-
if (subcommand === "dry-run") runNode("loop/oris-loop-dry-run.mjs", rest);
|
|
106
|
-
if (subcommand === "run") runNode("loop/oris-loop-run.mjs", rest);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
63
|
if (command === "flow") {
|
|
110
64
|
if (subcommand === "scan") runNode("flow/oris-flow-scan.mjs", rest);
|
|
111
65
|
if (subcommand === "version-control") runNode("flow/oris-flow-version-control.mjs", rest);
|
|
112
|
-
if (subcommand === "clean-runtime") runNode("flow/oris-flow-clean-runtime.mjs", rest);
|
|
113
66
|
}
|
|
114
67
|
|
|
115
68
|
console.error(`Unknown command: ${[command, subcommand].filter(Boolean).join(" ")}`);
|
|
@@ -9,21 +9,13 @@ const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", ".
|
|
|
9
9
|
|
|
10
10
|
const nodeTestFiles = [
|
|
11
11
|
"scripts/tests/test-schemas.mjs",
|
|
12
|
-
"scripts/tests/test-oris-loop-document.mjs",
|
|
13
|
-
"scripts/tests/test-agent-adapters.mjs",
|
|
14
12
|
"scripts/tests/test-skill-self-contained.mjs",
|
|
15
|
-
"scripts/tests/test-
|
|
13
|
+
"scripts/tests/test-cleanliness.mjs",
|
|
16
14
|
"scripts/tests/test-oris-flow-scan.mjs",
|
|
17
15
|
"scripts/tests/test-oris-flow-devops.mjs",
|
|
18
16
|
"scripts/tests/test-install-mcp.mjs",
|
|
19
17
|
"scripts/tests/test-routing-lifecycle.mjs",
|
|
20
18
|
"scripts/tests/test-skill-style.mjs",
|
|
21
|
-
"scripts/tests/test-oris-loop-stop.mjs",
|
|
22
|
-
"scripts/tests/test-oris-loop-smoke.mjs",
|
|
23
|
-
"scripts/tests/test-oris-loop-bootstrap.mjs",
|
|
24
|
-
"scripts/tests/test-oris-flow-clean-runtime.mjs",
|
|
25
|
-
"scripts/tests/test-oris-loop-list.mjs",
|
|
26
|
-
"scripts/tests/test-oris-loop-run.mjs",
|
|
27
19
|
"scripts/tests/test-oris-gitignore.mjs",
|
|
28
20
|
];
|
|
29
21
|
|