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.
Files changed (86) hide show
  1. package/.cursor-plugin/plugin.json +2 -3
  2. package/CHANGELOG.md +24 -6
  3. package/README.md +28 -60
  4. package/docs/architecture.md +15 -11
  5. package/docs/distribution.md +3 -3
  6. package/docs/maintainer-guide.md +4 -4
  7. package/docs/user-guide.md +12 -20
  8. package/package.json +3 -6
  9. package/references/clean-code-checklist.md +2 -2
  10. package/references/conventions.md +36 -16
  11. package/references/doc-policy.md +5 -5
  12. package/references/research.md +45 -0
  13. package/references/settings.md +11 -5
  14. package/references/settings.schema.json +10 -0
  15. package/scripts/flow/oris-flow-layout.mjs +0 -16
  16. package/scripts/flow/oris-flow-scan.mjs +32 -317
  17. package/scripts/flow/oris-gitignore.mjs +1 -5
  18. package/scripts/install/install-user-skills.mjs +3 -3
  19. package/scripts/install/uninstall-user-skills.mjs +2 -29
  20. package/scripts/oris-skills.mjs +0 -47
  21. package/scripts/tests/run-all-tests.mjs +1 -9
  22. package/scripts/tests/test-cleanliness.mjs +54 -0
  23. package/scripts/tests/test-oris-flow-scan.mjs +21 -91
  24. package/scripts/tests/test-routing-lifecycle.mjs +24 -54
  25. package/scripts/tests/test-schemas.mjs +17 -31
  26. package/scripts/tests/test-skill-style.mjs +5 -2
  27. package/skills/oris-flow/SKILL.md +21 -11
  28. package/skills/oris-flow/agents/openai.yaml +1 -1
  29. package/skills/oris-flow/references/architecture.md +6 -3
  30. package/skills/oris-flow/references/change.md +3 -0
  31. package/skills/oris-flow/references/commit.md +9 -8
  32. package/skills/oris-flow/references/criteria.md +9 -5
  33. package/skills/oris-flow/references/discover.md +2 -2
  34. package/skills/oris-flow/references/docs.md +5 -1
  35. package/skills/oris-flow/references/fix.md +10 -5
  36. package/skills/oris-flow/references/handoff.md +67 -0
  37. package/skills/oris-flow/references/help.md +3 -6
  38. package/skills/oris-flow/references/implement.md +7 -4
  39. package/skills/oris-flow/references/new.md +6 -6
  40. package/skills/oris-flow/references/plan.md +7 -2
  41. package/skills/oris-flow/references/pr.md +2 -2
  42. package/skills/oris-flow/references/setup.md +33 -39
  43. package/skills/oris-flow/references/triage.md +69 -0
  44. package/skills/oris-flow/references/verify.md +5 -5
  45. package/agents/oris-loop-debriefer.md +0 -30
  46. package/agents/oris-loop-doctor.md +0 -32
  47. package/agents/oris-loop-executor.md +0 -35
  48. package/agents/oris-loop-verifier.md +0 -35
  49. package/references/loop-adapter.schema.json +0 -93
  50. package/references/loop-contract.md +0 -126
  51. package/references/loop.schema.json +0 -156
  52. package/references/repo-map.md +0 -51
  53. package/references/repo-map.schema.json +0 -213
  54. package/scripts/flow/oris-flow-clean-runtime.mjs +0 -182
  55. package/scripts/install/generate-agent-adapters.mjs +0 -81
  56. package/scripts/loop/oris-loop-bootstrap.mjs +0 -383
  57. package/scripts/loop/oris-loop-bundle.mjs +0 -22
  58. package/scripts/loop/oris-loop-chat.mjs +0 -396
  59. package/scripts/loop/oris-loop-demo.mjs +0 -171
  60. package/scripts/loop/oris-loop-document.mjs +0 -196
  61. package/scripts/loop/oris-loop-dry-run.mjs +0 -114
  62. package/scripts/loop/oris-loop-fixtures.mjs +0 -149
  63. package/scripts/loop/oris-loop-list.mjs +0 -81
  64. package/scripts/loop/oris-loop-paths.mjs +0 -232
  65. package/scripts/loop/oris-loop-run.mjs +0 -301
  66. package/scripts/loop/oris-loop-stop.mjs +0 -358
  67. package/scripts/loop/oris-loop-templates.mjs +0 -80
  68. package/scripts/loop/oris-loop-verify.mjs +0 -205
  69. package/scripts/tests/test-agent-adapters.mjs +0 -70
  70. package/scripts/tests/test-oris-1-0-cleanliness.mjs +0 -58
  71. package/scripts/tests/test-oris-flow-clean-runtime.mjs +0 -75
  72. package/scripts/tests/test-oris-loop-bootstrap.mjs +0 -94
  73. package/scripts/tests/test-oris-loop-document.mjs +0 -148
  74. package/scripts/tests/test-oris-loop-list.mjs +0 -74
  75. package/scripts/tests/test-oris-loop-run.mjs +0 -71
  76. package/scripts/tests/test-oris-loop-smoke.mjs +0 -120
  77. package/scripts/tests/test-oris-loop-stop.mjs +0 -432
  78. package/skills/oris-flow/references/loop-craft.md +0 -59
  79. package/skills/oris-flow/references/loop-improve.md +0 -32
  80. package/skills/oris-flow/references/loop-run.md +0 -47
  81. package/skills/oris-flow/references/loop.md +0 -56
  82. package/skills/oris-flow/templates/debriefer.md +0 -19
  83. package/skills/oris-flow/templates/doctor.md +0 -22
  84. package/skills/oris-flow/templates/executor.md +0 -25
  85. package/skills/oris-flow/templates/orchestrator.md +0 -36
  86. package/skills/oris-flow/templates/verifier.md +0 -24
@@ -1,196 +0,0 @@
1
- import fs from "node:fs";
2
- import path from "node:path";
3
- import { ORIS_LOOP_DOCUMENT_VERSION } from "../flow/oris-flow-layout.mjs";
4
-
5
- const slugPattern = /^[a-z0-9]+(?:[._-][a-z0-9]+)*$/;
6
- const phasePattern = /^[a-z][a-z0-9-]*$/;
7
-
8
- export const INHERIT_MODEL = "inherit";
9
- export const ROLE_NAMES = ["executor", "verifier", "doctor", "debriefer"];
10
- export const DEFAULT_ROLES = ["executor", "verifier"];
11
- export const IMPROVE_MODES = new Set(["propose", "auto"]);
12
-
13
- export function parseFrontMatter(text, filePath = "loop.md") {
14
- const normalized = String(text ?? "").replace(/^\uFEFF/, "");
15
- if (!normalized.startsWith("---\n") && !normalized.startsWith("---\r\n")) {
16
- throw new Error(`${filePath} must start with front matter.`);
17
- }
18
- const match = normalized.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?/);
19
- if (!match) throw new Error(`${filePath} front matter is not closed.`);
20
- return {
21
- data: parseSimpleYaml(match[1], filePath),
22
- body: normalized.slice(match[0].length),
23
- };
24
- }
25
-
26
- function parseSimpleYaml(source, filePath) {
27
- const root = {};
28
- const stack = [{ indent: -1, value: root }];
29
- const lines = source.split(/\r?\n/);
30
-
31
- for (let index = 0; index < lines.length; index += 1) {
32
- const raw = lines[index];
33
- if (!raw.trim() || raw.trimStart().startsWith("#")) continue;
34
- const indent = raw.match(/^ */)?.[0].length ?? 0;
35
- const trimmed = raw.trim();
36
- const match = trimmed.match(/^([A-Za-z0-9_-]+):(?:\s*(.*))?$/);
37
- if (!match) throw new Error(`${filePath} front matter line ${index + 1} is not supported: ${raw}`);
38
-
39
- while (stack.length > 1 && indent <= stack[stack.length - 1].indent) stack.pop();
40
- const parent = stack[stack.length - 1].value;
41
- const key = match[1];
42
- const valueText = match[2] ?? "";
43
- if (valueText === "") {
44
- const value = {};
45
- parent[key] = value;
46
- stack.push({ indent, value });
47
- } else {
48
- parent[key] = parseScalar(valueText);
49
- }
50
- }
51
-
52
- return root;
53
- }
54
-
55
- function parseScalar(valueText) {
56
- const value = valueText.trim();
57
- if (value === "true") return true;
58
- if (value === "false") return false;
59
- if (/^-?\d+$/.test(value)) return Number.parseInt(value, 10);
60
- if (value.startsWith("[") && value.endsWith("]")) {
61
- const inner = value.slice(1, -1).trim();
62
- if (!inner) return [];
63
- return inner.split(",").map((item) => stripQuotes(item.trim())).filter(Boolean);
64
- }
65
- return stripQuotes(value);
66
- }
67
-
68
- function stripQuotes(value) {
69
- if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
70
- return value.slice(1, -1);
71
- }
72
- return value;
73
- }
74
-
75
- export function readLoopDocument(filePath) {
76
- return parseFrontMatter(fs.readFileSync(filePath, "utf8"), filePath);
77
- }
78
-
79
- export function validateLoopDocumentText(text, filePath = "loop.md") {
80
- const parsed = parseFrontMatter(text, filePath);
81
- validateLoopMetadata(parsed.data, filePath);
82
- if (!parsed.body.trim()) {
83
- throw new Error(`${filePath} body must describe the loop goal and stop condition.`);
84
- }
85
- return parsed;
86
- }
87
-
88
- export function validateLoopMetadata(data, filePath = "loop.md") {
89
- const errors = [];
90
- if (data.schemaVersion !== ORIS_LOOP_DOCUMENT_VERSION) {
91
- errors.push(`schemaVersion must be ${ORIS_LOOP_DOCUMENT_VERSION}`);
92
- }
93
- if (data.kind !== "oris-loop") errors.push("kind must be oris-loop");
94
- if (typeof data.name !== "string" || !slugPattern.test(data.name)) errors.push("name must be a stable slug");
95
- if (data.approved !== true) errors.push("approved must be true (the user approves the loop before it can run)");
96
- if (!Array.isArray(data.phases) || data.phases.length < 1 || data.phases.some((phase) => typeof phase !== "string" || !phasePattern.test(phase))) {
97
- errors.push("phases must contain valid phase slugs");
98
- }
99
- if (data.roles !== undefined) {
100
- if (!Array.isArray(data.roles) || data.roles.some((role) => !ROLE_NAMES.includes(role))) {
101
- errors.push(`roles must be an array of ${ROLE_NAMES.join("|")}`);
102
- } else {
103
- if (new Set(data.roles).size !== data.roles.length) errors.push("roles must not repeat");
104
- for (const required of DEFAULT_ROLES) {
105
- if (!data.roles.includes(required)) errors.push(`roles must include ${required}`);
106
- }
107
- }
108
- }
109
- if (data.improve?.mode === "auto" && !activeRoles(data).includes("debriefer")) {
110
- errors.push("improve.mode auto requires the debriefer role (roles: [executor, verifier, debriefer])");
111
- }
112
- if (!data.limits || !Number.isInteger(data.limits.maxIterations) || data.limits.maxIterations < 1 || data.limits.maxIterations > 500) {
113
- errors.push("limits.maxIterations must be 1..500");
114
- }
115
- if (!data.limits || !Number.isInteger(data.limits.maxNoProgress) || data.limits.maxNoProgress < 1 || data.limits.maxNoProgress > 50) {
116
- errors.push("limits.maxNoProgress must be 1..50");
117
- }
118
- if (data.models !== undefined) {
119
- if (!data.models || typeof data.models !== "object" || Array.isArray(data.models)) {
120
- errors.push("models must be an object when present");
121
- } else {
122
- for (const key of ["default", ...ROLE_NAMES]) {
123
- const value = data.models[key];
124
- if (value !== undefined && (typeof value !== "string" || !value.trim())) {
125
- errors.push(`models.${key} must be a non-empty string (model id, adapter alias, or "inherit")`);
126
- }
127
- }
128
- }
129
- }
130
- if (data.improve !== undefined) {
131
- if (!data.improve || typeof data.improve !== "object" || Array.isArray(data.improve)) {
132
- errors.push("improve must be an object when present");
133
- } else if (data.improve.mode !== undefined && !IMPROVE_MODES.has(data.improve.mode)) {
134
- errors.push('improve.mode must be "propose" or "auto"');
135
- }
136
- }
137
- if (!data.permissions || !Array.isArray(data.permissions.allowedPaths)) errors.push("permissions.allowedPaths must be an array");
138
- if (!data.permissions || !Array.isArray(data.permissions.forbiddenActions) || data.permissions.forbiddenActions.length === 0) {
139
- errors.push("permissions.forbiddenActions must be a non-empty array");
140
- }
141
- if (!data.verification || !Array.isArray(data.verification.commands)) errors.push("verification.commands must be an array");
142
- if (!data.verification || typeof data.verification.evidenceRequired !== "boolean") {
143
- errors.push("verification.evidenceRequired must be boolean");
144
- }
145
- if (errors.length > 0) throw new Error(`${filePath} is not a valid Oris Loop v${ORIS_LOOP_DOCUMENT_VERSION} document: ${errors.join("; ")}.`);
146
- return data;
147
- }
148
-
149
- /**
150
- * Resolve per-role models. `inherit` means "use the model of the current
151
- * session"; any other value is an adapter alias or a platform model id.
152
- */
153
- export function normalizeModels(models = {}, adapterModels = {}) {
154
- const fallback = resolveModelAlias(models.default ?? INHERIT_MODEL, adapterModels);
155
- const resolved = { default: fallback };
156
- for (const role of ROLE_NAMES) {
157
- resolved[role] = models[role] !== undefined
158
- ? resolveModelAlias(models[role], adapterModels)
159
- : fallback;
160
- }
161
- return resolved;
162
- }
163
-
164
- function resolveModelAlias(value, adapterModels) {
165
- const name = String(value ?? INHERIT_MODEL).trim() || INHERIT_MODEL;
166
- if (name === INHERIT_MODEL) return INHERIT_MODEL;
167
- if (adapterModels && typeof adapterModels === "object" && typeof adapterModels[name] === "string") {
168
- return adapterModels[name];
169
- }
170
- return name;
171
- }
172
-
173
- export function improveMode(data) {
174
- return data?.improve?.mode === "auto" ? "auto" : "propose";
175
- }
176
-
177
- /** Roles a loop runs each pass; executor + verifier unless loop.md opts more in. */
178
- export function activeRoles(data) {
179
- return Array.isArray(data?.roles) && data.roles.length > 0 ? [...data.roles] : [...DEFAULT_ROLES];
180
- }
181
-
182
- /** Check the per-loop prompts/ directory against the loop's active roles. */
183
- export function assessPromptFiles(loopDir, roles = DEFAULT_ROLES) {
184
- const promptsDir = path.join(loopDir, "prompts");
185
- const missing = [];
186
- for (const role of roles) {
187
- const full = path.join(promptsDir, `${role}.md`);
188
- if (!fs.existsSync(full) || !fs.readFileSync(full, "utf8").trim()) missing.push(`prompts/${role}.md`);
189
- }
190
- return { promptsDir, missing };
191
- }
192
-
193
- export function loopPhasesFromMetadata(data) {
194
- validateLoopMetadata(data);
195
- return [...data.phases];
196
- }
@@ -1,114 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * `oris-skills loop dry-run --loop <slug>` — show exactly what the next pass
5
- * would inject into the chat, without arming the runtime or changing state.
6
- */
7
-
8
- import childProcess from "node:child_process";
9
- import fs from "node:fs";
10
- import path from "node:path";
11
- import process from "node:process";
12
- import { readAdapter, resolveLoopDocumentPathsBySlug } from "./oris-loop-paths.mjs";
13
- import {
14
- activeRoles,
15
- assessPromptFiles,
16
- improveMode,
17
- normalizeModels,
18
- validateLoopDocumentText,
19
- } from "./oris-loop-document.mjs";
20
- import { readTemplate, stripTemplateHeading, substitutePlaceholders } from "./oris-loop-templates.mjs";
21
-
22
- function fail(message) {
23
- console.error(`[oris-skills] ERROR ${message}`);
24
- process.exit(1);
25
- }
26
-
27
- function parseArgs(argv) {
28
- const options = { repositoryRoot: "", loopSlug: "" };
29
- for (let i = 0; i < argv.length; i += 1) {
30
- const raw = argv[i];
31
- if (raw.startsWith("--loop=")) options.loopSlug = raw.slice("--loop=".length);
32
- else if (raw === "--loop" && argv[i + 1]) {
33
- options.loopSlug = argv[i + 1];
34
- i += 1;
35
- } else if (raw.startsWith("--repository-root=")) options.repositoryRoot = raw.slice("--repository-root=".length);
36
- else if (raw === "--repository-root" && argv[i + 1]) {
37
- options.repositoryRoot = argv[i + 1];
38
- i += 1;
39
- }
40
- }
41
- return options;
42
- }
43
-
44
- function getRoot(options) {
45
- if (options.repositoryRoot) return path.resolve(options.repositoryRoot);
46
- const result = childProcess.spawnSync("git", ["rev-parse", "--show-toplevel"], {
47
- cwd: process.cwd(),
48
- shell: process.platform === "win32",
49
- encoding: "utf8",
50
- });
51
- if (result.status !== 0 || !result.stdout.trim()) fail("Run inside a Git worktree or pass --repository-root.");
52
- return path.resolve(result.stdout.trim());
53
- }
54
-
55
- const options = parseArgs(process.argv.slice(2));
56
- if (!options.loopSlug) fail("--loop <slug> is required. List loops with: npx oris-skills loop list");
57
- const root = getRoot(options);
58
- const adapter = readAdapter(root);
59
- const docs = resolveLoopDocumentPathsBySlug(root, options.loopSlug, adapter);
60
- if (!fs.existsSync(docs.loopFull)) fail(`Loop not found: ${docs.loopRelative}`);
61
-
62
- let loopDocument;
63
- try {
64
- loopDocument = validateLoopDocumentText(fs.readFileSync(docs.loopFull, "utf8"), docs.loopRelative);
65
- } catch (error) {
66
- fail(error.message);
67
- }
68
- const metadata = loopDocument.data;
69
- const roles = activeRoles(metadata);
70
- const prompts = assessPromptFiles(path.dirname(docs.loopFull), roles);
71
- const models = normalizeModels(metadata.models, adapter.models);
72
-
73
- const perLoopOrchestrator = path.join(docs.promptsFull, "orchestrator.md");
74
- const template = fs.existsSync(perLoopOrchestrator)
75
- ? stripTemplateHeading(fs.readFileSync(perLoopOrchestrator, "utf8"))
76
- : readTemplate("orchestrator", import.meta.url);
77
-
78
- const followup = substitutePlaceholders(template, {
79
- iteration: 1,
80
- maxIterations: metadata.limits.maxIterations,
81
- loop: metadata.name,
82
- phase: metadata.phases[0],
83
- phaseIndex: 1,
84
- phaseCount: metadata.phases.length,
85
- instruction: "continue the current phase",
86
- loopPath: docs.loopRelative,
87
- contextPath: docs.contextRelative,
88
- promptsDir: docs.promptsRelative,
89
- roles: roles.join(", "),
90
- executorModel: models.executor,
91
- verifierModel: models.verifier,
92
- doctorModel: models.doctor,
93
- debrieferModel: models.debriefer,
94
- improveMode: improveMode(metadata),
95
- taskPath: docs.taskPath,
96
- });
97
-
98
- console.log(`Oris loop dry run: ${metadata.name}`);
99
- console.log(`Definition: ${docs.loopRelative} (schemaVersion ${metadata.schemaVersion}, approved: ${metadata.approved})`);
100
- console.log(`Prompts: ${docs.promptsRelative} ${prompts.missing.length === 0 ? "(complete)" : `(MISSING: ${prompts.missing.join(", ")})`}`);
101
- console.log(`Roles: ${roles.join(", ")}`);
102
- console.log(`Models: executor=${models.executor} verifier=${models.verifier} doctor=${models.doctor} debriefer=${models.debriefer}`);
103
- console.log(`Improve mode: ${improveMode(metadata)}`);
104
- console.log(`Limits: ${metadata.limits.maxIterations} iterations, ${metadata.limits.maxNoProgress} no-progress, ${metadata.limits.maxMinutes ?? 240} minutes`);
105
- console.log("");
106
- console.log("=== Message the stop hook would inject for pass 1 ===");
107
- console.log(followup);
108
- console.log("=== End of injected message ===");
109
- console.log("");
110
- if (prompts.missing.length > 0) {
111
- console.log("NOT runnable yet — create the missing prompt files first (templates: skills/oris-flow/templates/).");
112
- process.exit(1);
113
- }
114
- console.log(`Runnable. Start it with: npx oris-skills loop chat --action start --loop ${metadata.name}`);
@@ -1,149 +0,0 @@
1
- import fs from "node:fs";
2
- import path from "node:path";
3
- import { spawnSync } from "node:child_process";
4
- import {
5
- bootstrapLoop,
6
- buildAdapterDraft,
7
- writeHooksRegistration,
8
- writeStopHookWrapper,
9
- } from "./oris-loop-bootstrap.mjs";
10
- import { defaultLoopDocumentPaths, readAdapter } from "./oris-loop-paths.mjs";
11
- import { ORIS_FLOW_ADAPTER, ORIS_FLOW_TASKS_ROOT } from "../flow/oris-flow-layout.mjs";
12
-
13
- export function writeMinimalAdapter(root) {
14
- const adapterPath = path.join(root, ORIS_FLOW_ADAPTER);
15
- fs.mkdirSync(path.dirname(adapterPath), { recursive: true });
16
- const adapter = buildAdapterDraft(root);
17
- adapter.preflight.minFreeDiskGiB = 0;
18
- fs.writeFileSync(adapterPath, `${JSON.stringify(adapter, null, 2)}\n`, "utf8");
19
- return adapterPath;
20
- }
21
-
22
- export function loopDocumentSource({
23
- program = "oris-loop-smoke",
24
- phases = ["work"],
25
- roles = ["executor", "verifier"],
26
- improve = "propose",
27
- models = {},
28
- goal = "Run one deterministic smoke action, verify it, and record evidence.",
29
- stop = "Stop when the smoke check passes once, or when blocked.",
30
- } = {}) {
31
- const modelLines = ["models:", ` default: ${models.default ?? "inherit"}`];
32
- for (const role of ["executor", "verifier", "doctor", "debriefer"]) {
33
- if (models[role]) modelLines.push(` ${role}: ${models[role]}`);
34
- }
35
- return `---
36
- schemaVersion: 2
37
- kind: oris-loop
38
- name: ${program}
39
- approved: true
40
- phases: [${phases.join(", ")}]
41
- roles: [${roles.join(", ")}]
42
- limits:
43
- maxIterations: 10
44
- maxNoProgress: 2
45
- maxMinutes: 240
46
- ${modelLines.join("\n")}
47
- improve:
48
- mode: ${improve}
49
- permissions:
50
- allowedPaths: [.oris-flow/loops, .oris-flow/runtime]
51
- forbiddenActions: [commit, push, deploy, external-message]
52
- verification:
53
- commands: [node --version]
54
- evidenceRequired: true
55
- ---
56
-
57
- # ${program}
58
-
59
- Goal: ${goal}
60
- Stop: ${stop}
61
-
62
- Role prompts live in \`prompts/\`. Edit them there; the loop picks changes up on the next pass.
63
- `;
64
- }
65
-
66
- export function rolePromptFixtures() {
67
- return {
68
- "executor.md": `# Executor prompt
69
-
70
- ORIS LOOP ACTIVE — approved smoke pass.
71
- Perform exactly one bounded smoke action inside allowed paths.
72
- Runtime: iteration {{iteration}}, phase {{phase}}, target {{currentTarget}}.
73
- RETURN compact JSON (\`\`\`json fenced): { "role": "executor", "state": "claimed|blocked", "summary": "", "changedFiles": [], "claim": "" }
74
- `,
75
- "verifier.md": `# Verifier prompt
76
-
77
- Verify independently: \`node --version\` exits 0. NEVER trust the executor without running the check.
78
- Runtime: iteration {{iteration}}; executor claim supplied by orchestrator.
79
- goalMet = true when the smoke check passed (the loop goal is one verified pass).
80
- RETURN compact JSON (\`\`\`json fenced): { "role": "verifier", "state": "pass|fail|blocked|inconclusive", "goalMet": false, "summary": "", "evidence": "" }
81
- `,
82
- };
83
- }
84
-
85
- export function writeLoopTask(root, {
86
- taskSlug = "oris-loop-smoke",
87
- program = "oris-loop-smoke",
88
- phases = ["work"],
89
- phase = phases[0],
90
- roles = ["executor", "verifier"],
91
- improve = "propose",
92
- models = {},
93
- } = {}) {
94
- const taskPath = `${ORIS_FLOW_TASKS_ROOT}/${taskSlug}`;
95
- fs.mkdirSync(path.join(root, ...taskPath.split("/")), { recursive: true });
96
- const adapter = readAdapter(root);
97
- const loopDocs = defaultLoopDocumentPaths(root, taskPath, adapter);
98
- const loopPath = loopDocs.loopFull;
99
- const contextPath = loopDocs.contextFull;
100
- fs.mkdirSync(path.dirname(loopPath), { recursive: true });
101
- fs.writeFileSync(loopPath, loopDocumentSource({ program, phases, roles, improve, models }), "utf8");
102
- fs.mkdirSync(loopDocs.promptsFull, { recursive: true });
103
- for (const [name, content] of Object.entries(rolePromptFixtures())) {
104
- fs.writeFileSync(path.join(loopDocs.promptsFull, name), content, "utf8");
105
- }
106
- fs.writeFileSync(contextPath, `# Oris Loop Context
107
-
108
- Run: inactive
109
- State: ready
110
- Current program: ${program}
111
- Current phase: ${phase}
112
- Current phase index: 0
113
- Current iteration: 0
114
- Phase iteration: 0
115
- No-progress streak: 0
116
- Updated: ${new Date().toISOString()}
117
-
118
- ## Objective
119
-
120
- Verify the Oris Loop same-chat stop hook.
121
-
122
- ## Next action
123
-
124
- Run one smoke pass.
125
- `, "utf8");
126
- return {
127
- taskPath,
128
- taskRoot: path.join(root, ".oris-flow", "loops", taskSlug),
129
- loopPath,
130
- contextPath,
131
- promptsPath: loopDocs.promptsFull,
132
- };
133
- }
134
-
135
- export function writeStopHookRegistration(root) {
136
- return writeHooksRegistration(root, "cursor");
137
- }
138
-
139
- export { writeStopHookWrapper };
140
-
141
- export function initSmokeRepository(root, options = {}) {
142
- fs.mkdirSync(root, { recursive: true });
143
- const git = spawnSync("git", ["init", "-q"], { cwd: root, encoding: "utf8" });
144
- if (git.status !== 0) {
145
- throw new Error(`git init failed: ${git.stderr}`);
146
- }
147
- bootstrapLoop(root);
148
- return writeLoopTask(root, options);
149
- }
@@ -1,81 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import childProcess from "node:child_process";
4
- import fs from "node:fs";
5
- import path from "node:path";
6
- import process from "node:process";
7
- import { discoverExistingLoops, readAdapter } from "./oris-loop-paths.mjs";
8
- import { ORIS_FLOW_ADAPTER, ORIS_FLOW_RUNTIME } from "../flow/oris-flow-layout.mjs";
9
-
10
- function fail(message) {
11
- throw new Error(message);
12
- }
13
-
14
- function parseArgs(argv) {
15
- const options = {
16
- repositoryRoot: "",
17
- json: false,
18
- };
19
- const readValue = (index, raw) => {
20
- if (raw.includes("=")) return [index, raw.slice(raw.indexOf("=") + 1)];
21
- if (index + 1 >= argv.length) fail(`Missing value for ${raw}.`);
22
- return [index + 1, argv[index + 1]];
23
- };
24
- for (let i = 0; i < argv.length; i += 1) {
25
- const raw = argv[i];
26
- const key = raw.toLowerCase();
27
- if (key === "--repository-root" || key.startsWith("--repository-root=")) [i, options.repositoryRoot] = readValue(i, raw);
28
- else if (key === "--json") options.json = true;
29
- else fail(`Unknown argument: ${raw}`);
30
- }
31
- return options;
32
- }
33
-
34
- function run(command, args, cwd) {
35
- return childProcess.spawnSync(command, args, {
36
- cwd,
37
- shell: process.platform === "win32",
38
- encoding: "utf8",
39
- });
40
- }
41
-
42
- function getRoot(options) {
43
- if (options.repositoryRoot) return path.resolve(options.repositoryRoot);
44
- const result = run("git", ["rev-parse", "--show-toplevel"], process.cwd());
45
- if (result.status !== 0 || !result.stdout.trim()) fail("Run inside a Git worktree or pass --repository-root.");
46
- return path.resolve(result.stdout.trim());
47
- }
48
-
49
- const options = parseArgs(process.argv.slice(2));
50
- const root = getRoot(options);
51
- const adapterPath = path.join(root, ORIS_FLOW_ADAPTER);
52
- const adapter = fs.existsSync(adapterPath) ? readAdapter(root, adapterPath) : readAdapter(root);
53
- const loops = discoverExistingLoops(root, adapter);
54
- const payload = {
55
- repositoryRoot: root.replace(/\\/g, "/"),
56
- runtimeRelative: adapter?.paths?.runtime ?? ORIS_FLOW_RUNTIME,
57
- count: loops.length,
58
- loops,
59
- };
60
-
61
- if (options.json) {
62
- console.log(JSON.stringify(payload, null, 2));
63
- process.exit(0);
64
- }
65
-
66
- if (loops.length === 0) {
67
- console.log("No existing Oris loops found.");
68
- process.exit(0);
69
- }
70
-
71
- console.log(`Existing Oris loops (${loops.length}):`);
72
- for (const loop of loops) {
73
- console.log("");
74
- console.log(`- slug: ${loop.slug}`);
75
- console.log(` task: ${loop.taskPath}`);
76
- console.log(` program: ${loop.program || "custom"}`);
77
- console.log(` run/state: ${loop.run || "inactive"} / ${loop.state || "unknown"}`);
78
- console.log(` loop: ${loop.loopRelative}`);
79
- console.log(` context: ${loop.contextRelative}`);
80
- if (loop.objective) console.log(` objective: ${loop.objective}`);
81
- }