oris-skills 2.2.1 → 2.2.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/CHANGELOG.md +8 -0
- package/README.md +4 -3
- package/docs/adr/README.md +5 -0
- package/docs/distribution.md +1 -2
- package/docs/user-guide.md +1 -1
- package/package.json +1 -1
- package/references/conventions.md +24 -6
- package/references/doc-policy.md +1 -1
- package/references/loop-adapter.schema.json +1 -34
- package/references/repo-map.md +2 -4
- package/references/repo-map.schema.json +0 -26
- package/references/settings.md +8 -20
- package/references/settings.schema.json +30 -80
- package/scripts/flow/oris-flow-layout.mjs +1 -1
- package/scripts/flow/oris-flow-scan.mjs +3 -9
- package/scripts/install/install-user-skills.mjs +25 -28
- package/scripts/loop/oris-loop-bootstrap.mjs +0 -5
- package/scripts/loop/oris-loop-chat.mjs +0 -30
- package/scripts/tests/run-all-tests.mjs +1 -0
- package/scripts/tests/test-oris-loop-smoke.mjs +1 -44
- package/scripts/tests/test-schemas.mjs +9 -8
- package/scripts/tests/test-skill-self-contained.mjs +66 -0
- package/skills/oris-flow/SKILL.md +3 -1
- package/skills/oris-flow/references/help.md +1 -1
- package/skills/oris-flow/references/setup.md +12 -10
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## Unreleased
|
|
4
|
+
|
|
5
|
+
- **Settings are per-project and tiny**: moved out of the user-global `~/.oris/settings.json` into `.oris-flow/settings.json`, holding only `orisFlow.responseStyle` (`standard`/`caveman`) and `orisFlow.artifactLanguage` (document language, default `en`). The installer no longer creates a settings file.
|
|
6
|
+
- **Test profiles removed**: the whole profile/secret subsystem is gone — `defaultProfiles`, `profiles`, the adapter `profile` block and its env mappings, and the loop's profile-gating. Every project drives its own e2e/CI tooling; Oris stores no credentials.
|
|
7
|
+
- **No UI-language setting**: chat, menus, and question labels follow the language the user writes (deduced from context); only generated documents use `artifactLanguage`.
|
|
8
|
+
- **No refresh-policy setting**: map freshness is answered by the `headSha` anchor (`flow scan --check`); the advisory `refreshPolicy` field was dropped from the manifest and schema.
|
|
9
|
+
- **Questions & subagents fire reliably**: conventions now bind a platform question call (Claude `AskUserQuestion`) as a tool call rather than optional chat text, and treat entering an Oris route as the explicit request to spawn scan subagents — so gates open their UI and setup fans out by default.
|
|
10
|
+
|
|
3
11
|
## 2.2.1 - 2026-07-07
|
|
4
12
|
|
|
5
13
|
- **Loop roles are invisible to users**: the installer no longer writes `oris-loop-{executor,verifier,doctor,debriefer}` into `~/.cursor/agents/`, `~/.claude/agents/`, or `~/.codex/agents/` — they showed up as manually invocable subagents, but the loop orchestrator never spawns them by name (it passes each loop's `prompts/<role>.md` to a generic subagent). `agents/` stays in the repo as the canonical role definitions for development. Uninstall (and therefore reinstall/upgrade) now also removes adapters left behind by earlier versions.
|
package/README.md
CHANGED
|
@@ -91,9 +91,10 @@ Stop anything, anytime: `npx oris-skills loop chat --action stop`.
|
|
|
91
91
|
|
|
92
92
|
## Tuning
|
|
93
93
|
|
|
94
|
-
Settings live in
|
|
95
|
-
|
|
96
|
-
|
|
94
|
+
Settings live in `.oris-flow/settings.json` (`/oris-flow` → Setup): document language
|
|
95
|
+
(`artifactLanguage`, default `en`) and `responseStyle` (`standard`/`caveman`, where
|
|
96
|
+
caveman shrinks chat to maximum token density while documents and safety gates stay
|
|
97
|
+
complete). Chat language is deduced from your messages, not configured.
|
|
97
98
|
|
|
98
99
|
## Repository layout
|
|
99
100
|
|
package/docs/distribution.md
CHANGED
|
@@ -14,13 +14,12 @@ The installer detects Cursor / Claude Code / Codex from `~/.cursor`, `~/.claude`
|
|
|
14
14
|
|
|
15
15
|
```text
|
|
16
16
|
~/.oris/oris-skills/ bundle: scripts, skills, references, agents, docs
|
|
17
|
-
~/.oris/settings.json user settings (created only if missing)
|
|
18
17
|
~/.cursor/skills/<name>/ Cursor skills (marker-managed)
|
|
19
18
|
~/.claude/skills/<name>/ Claude Code skills (marker-managed)
|
|
20
19
|
~/.codex/prompts/<name>.md Codex prompts pointing to the bundled SKILL.md
|
|
21
20
|
```
|
|
22
21
|
|
|
23
|
-
Project assets live only in each repository's `.oris-flow/` (adapter, loops, runtime) plus thin hook wrappers in `.cursor/hooks/` and `.claude/hooks/` written by `npx oris-skills loop bootstrap`.
|
|
22
|
+
Project assets live only in each repository's `.oris-flow/` (adapter, settings, loops, runtime) plus thin hook wrappers in `.cursor/hooks/` and `.claude/hooks/` written by `npx oris-skills loop bootstrap`.
|
|
24
23
|
|
|
25
24
|
## Uninstall / reinstall
|
|
26
25
|
|
package/docs/user-guide.md
CHANGED
|
@@ -42,4 +42,4 @@ npx oris-skills reinstall
|
|
|
42
42
|
```
|
|
43
43
|
|
|
44
44
|
Uninstall removes only Oris-owned paths (including legacy locations); preview with `--dry-run`.
|
|
45
|
-
Settings (
|
|
45
|
+
Settings (`artifactLanguage` for document language, `responseStyle: caveman` for ultra-short chat): `.oris-flow/settings.json`, edited via `/oris-flow` → Setup. Chat language is deduced from context, not configured.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "oris-skills",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.2",
|
|
4
4
|
"description": "Oris Skills: one entry point (/oris-flow), guided discovery-to-implementation flow, and self-tuning verified loops for Cursor, Claude Code, and Codex.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Davide Baldassarre",
|
|
@@ -26,9 +26,18 @@ The `oris-skills` CLI is NOT installed globally. ALWAYS invoke it as `npx oris-s
|
|
|
26
26
|
|
|
27
27
|
## Questions
|
|
28
28
|
|
|
29
|
-
- ASK
|
|
29
|
+
- ASK through the platform's question tool — Claude `AskUserQuestion`, Cursor `AskQuestion`
|
|
30
|
+
(Plan Mode). A question turn = ONE call to it. Chat text is a fallback, NOT a choice: use
|
|
31
|
+
it ONLY when no such tool is available (e.g. Cursor Agent Mode) or the question cannot fit
|
|
32
|
+
the tool's schema (open-ended, or more options than it allows) — then present the options
|
|
33
|
+
as a lettered list.
|
|
34
|
+
- Oris gates and interview questions ALWAYS qualify for the tool: the answer decides the
|
|
35
|
+
next step. NEVER skip the tool because an answer "looks like a sensible default".
|
|
36
|
+
- FIT the schema: question tools cap the options (typically 4, meta-options included).
|
|
37
|
+
MORE options → split into a two-level question (category first) or use the deliberate
|
|
38
|
+
text fallback above.
|
|
30
39
|
- ONE question per turn. Recommended answer FIRST.
|
|
31
|
-
- ALWAYS include,
|
|
40
|
+
- ALWAYS include, in the user's language:
|
|
32
41
|
- `Explain the options` / `Spiega le opzioni` — explain, then re-ask the same question.
|
|
33
42
|
- During interviews: `Enough questions — proceed` / `Basta domande — procedi` (early exit → go to the generation gate; NOT approval to write).
|
|
34
43
|
- NEVER ask what the workspace, docs, code, or setup map already answer. Explore first.
|
|
@@ -41,12 +50,15 @@ The `oris-skills` CLI is NOT installed globally. ALWAYS invoke it as `npx oris-s
|
|
|
41
50
|
|
|
42
51
|
## Language
|
|
43
52
|
|
|
44
|
-
-
|
|
45
|
-
|
|
53
|
+
- CHAT, menus, and question labels: the language the user writes — DEDUCED from context,
|
|
54
|
+
no setting. NEVER ask which language to use when the conversation already shows it.
|
|
55
|
+
- GENERATED documents: `orisFlow.artifactLanguage` from `.oris-flow/settings.json`
|
|
56
|
+
(default `en`).
|
|
57
|
+
- Skill instructions and code stay English.
|
|
46
58
|
|
|
47
59
|
## Response style
|
|
48
60
|
|
|
49
|
-
READ `orisFlow.responseStyle` (`standard` | `caveman`, default `standard`).
|
|
61
|
+
READ `orisFlow.responseStyle` from `.oris-flow/settings.json` (`standard` | `caveman`, default `standard`).
|
|
50
62
|
|
|
51
63
|
`caveman` = the user pays per token; chat goes maximum density. The contract, in its own voice:
|
|
52
64
|
|
|
@@ -75,5 +87,11 @@ labels, receipts, error reports, and anything another session must read cold.
|
|
|
75
87
|
|
|
76
88
|
## Subagents
|
|
77
89
|
|
|
78
|
-
- USE parallel subagents for independent scans when the platform
|
|
90
|
+
- USE parallel subagents for independent scans when the platform's agent tool is in the
|
|
91
|
+
tool list (Claude `Agent` tool — formerly Task; Cursor subagents).
|
|
92
|
+
- The user invoking an Oris route IS the explicit request to spawn: when a route step
|
|
93
|
+
names subagent scans, platform guidance like "only spawn when the user asks" is
|
|
94
|
+
satisfied — do not skip on that ground.
|
|
95
|
+
- WHEN the platform has no agent tool (or spawning is blocked) → run the same scans
|
|
96
|
+
inline, sequentially, same coverage. Coverage never shrinks; only parallelism does.
|
|
79
97
|
- KEEP subagent output compact: summaries and pointers, not raw logs.
|
package/references/doc-policy.md
CHANGED
|
@@ -32,7 +32,7 @@ NEVER split one feature's docs across unrelated directories.
|
|
|
32
32
|
|
|
33
33
|
## Rules
|
|
34
34
|
|
|
35
|
-
1. Language: documents in `artifactLanguage
|
|
35
|
+
1. Language: documents in `artifactLanguage` (default `en`); chat/UI language is deduced from context. Skill instructions and code stay English.
|
|
36
36
|
2. Steps are independent: any document may be created without the previous one. State the missing source once, use the strongest confirmed source, record the gap in the document ("not available" localized). Never block on a missing predecessor.
|
|
37
37
|
3. INSPECT the task folder + setup map before asking the user or writing.
|
|
38
38
|
4. Every document ends with a change-history table: date | source | change | reason. Later changes rewrite the affected sections AND add a history row — no inline "changed!" notes.
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"title": "Oris Loop Repository Adapter",
|
|
5
5
|
"type": "object",
|
|
6
6
|
"additionalProperties": false,
|
|
7
|
-
"required": ["$schema", "schemaVersion", "repository", "paths", "commands", "
|
|
7
|
+
"required": ["$schema", "schemaVersion", "repository", "paths", "commands", "preflight"],
|
|
8
8
|
"properties": {
|
|
9
9
|
"$schema": {
|
|
10
10
|
"const": "https://oris.dev/schemas/oris-loop-adapter-v1.json"
|
|
@@ -54,22 +54,6 @@
|
|
|
54
54
|
"propertyNames": { "pattern": "^[a-z][a-z0-9-]*$" },
|
|
55
55
|
"additionalProperties": { "type": "string", "minLength": 1 }
|
|
56
56
|
},
|
|
57
|
-
"profile": {
|
|
58
|
-
"type": "object",
|
|
59
|
-
"additionalProperties": false,
|
|
60
|
-
"required": ["required", "defaultRole", "mappings"],
|
|
61
|
-
"properties": {
|
|
62
|
-
"required": { "type": "boolean" },
|
|
63
|
-
"defaultRole": {
|
|
64
|
-
"type": "string",
|
|
65
|
-
"pattern": "^[a-z][a-zA-Z0-9]*$"
|
|
66
|
-
},
|
|
67
|
-
"mappings": {
|
|
68
|
-
"type": "array",
|
|
69
|
-
"items": { "$ref": "#/$defs/mapping" }
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
},
|
|
73
57
|
"preflight": {
|
|
74
58
|
"type": "object",
|
|
75
59
|
"additionalProperties": false,
|
|
@@ -104,23 +88,6 @@
|
|
|
104
88
|
"minimum": 0
|
|
105
89
|
}
|
|
106
90
|
}
|
|
107
|
-
},
|
|
108
|
-
"mapping": {
|
|
109
|
-
"type": "object",
|
|
110
|
-
"additionalProperties": false,
|
|
111
|
-
"required": ["env", "from", "required", "secret"],
|
|
112
|
-
"properties": {
|
|
113
|
-
"env": {
|
|
114
|
-
"type": "string",
|
|
115
|
-
"pattern": "^[A-Z_][A-Z0-9_]*$"
|
|
116
|
-
},
|
|
117
|
-
"from": {
|
|
118
|
-
"type": "string",
|
|
119
|
-
"pattern": "^[A-Za-z][A-Za-z0-9]*(\\.[A-Za-z][A-Za-z0-9]*)*$"
|
|
120
|
-
},
|
|
121
|
-
"required": { "type": "boolean" },
|
|
122
|
-
"secret": { "type": "boolean" }
|
|
123
|
-
}
|
|
124
91
|
}
|
|
125
92
|
}
|
|
126
93
|
}
|
package/references/repo-map.md
CHANGED
|
@@ -6,10 +6,10 @@ Setup map = the default project context for every Oris skill and loop. It exists
|
|
|
6
6
|
|
|
7
7
|
| File | Scope | Purpose |
|
|
8
8
|
|------|-------|---------|
|
|
9
|
-
| `.oris-flow/manifest.json` | repo | compact index: section paths, freshness, confidence
|
|
9
|
+
| `.oris-flow/manifest.json` | repo | compact index: section paths, freshness, confidence |
|
|
10
10
|
| `.oris-flow/maps/**` | repo | short markdown maps: docs, stack, commands, code areas, tests |
|
|
11
11
|
| `.oris-flow/adapter.json` | repo | loop adapter: paths, commands, model aliases |
|
|
12
|
-
|
|
|
12
|
+
| `.oris-flow/settings.json` | repo | preferences: `responseStyle` + `artifactLanguage` (non-secret) |
|
|
13
13
|
| `{tasksRoot}/{task}/…` | task | business artifacts only — `setup.tasksRoot` in the manifest, default `.oris-flow/tasks` (see `references/doc-policy.md`) |
|
|
14
14
|
|
|
15
15
|
Manifest and maps are never task authority and NEVER hold secrets.
|
|
@@ -24,7 +24,6 @@ Manifest and maps are never task authority and NEVER hold secrets.
|
|
|
24
24
|
"generatedAt": "",
|
|
25
25
|
"updatedAt": "",
|
|
26
26
|
"setup": { "versionControl": { "orisFlowMap": "commit" } },
|
|
27
|
-
"refreshPolicy": {},
|
|
28
27
|
"sections": {}
|
|
29
28
|
}
|
|
30
29
|
```
|
|
@@ -46,7 +45,6 @@ Each section: purpose, authoritative paths, confirmed decisions, key commands/co
|
|
|
46
45
|
2. VERIFY stale or high-risk facts against the repository before consequential actions.
|
|
47
46
|
3. Loops: the orchestrator passes relevant setup-map facts into role prompts at craft time; users never repeat project facts per loop.
|
|
48
47
|
4. SUGGEST `/oris-flow` → Setup refresh only when a stale gap materially affects the task.
|
|
49
|
-
5. Refresh policy is advisory — never a scheduler.
|
|
50
48
|
|
|
51
49
|
## Command surface
|
|
52
50
|
|
|
@@ -49,9 +49,6 @@
|
|
|
49
49
|
"setup": {
|
|
50
50
|
"$ref": "#/$defs/setup"
|
|
51
51
|
},
|
|
52
|
-
"refreshPolicy": {
|
|
53
|
-
"$ref": "#/$defs/refreshPolicy"
|
|
54
|
-
},
|
|
55
52
|
"sections": {
|
|
56
53
|
"$ref": "#/$defs/sections"
|
|
57
54
|
},
|
|
@@ -106,29 +103,6 @@
|
|
|
106
103
|
"pattern": "^[a-z][a-zA-Z0-9-]*$"
|
|
107
104
|
}
|
|
108
105
|
},
|
|
109
|
-
"refreshPolicy": {
|
|
110
|
-
"type": "object",
|
|
111
|
-
"additionalProperties": false,
|
|
112
|
-
"required": ["mode"],
|
|
113
|
-
"properties": {
|
|
114
|
-
"mode": {
|
|
115
|
-
"type": "string",
|
|
116
|
-
"enum": ["manual", "monthly", "after-major-refactor", "after-test-layout-change"]
|
|
117
|
-
},
|
|
118
|
-
"suggestAfterDays": {
|
|
119
|
-
"type": "integer",
|
|
120
|
-
"minimum": 1
|
|
121
|
-
},
|
|
122
|
-
"triggers": {
|
|
123
|
-
"type": "array",
|
|
124
|
-
"items": {
|
|
125
|
-
"type": "string",
|
|
126
|
-
"enum": ["after-major-refactor", "after-test-layout-change"]
|
|
127
|
-
},
|
|
128
|
-
"uniqueItems": true
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
},
|
|
132
106
|
"setup": {
|
|
133
107
|
"type": "object",
|
|
134
108
|
"additionalProperties": false,
|
package/references/settings.md
CHANGED
|
@@ -1,36 +1,24 @@
|
|
|
1
1
|
# Oris Settings
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
Per-project preferences. Path: `.oris-flow/settings.json`. Small and non-secret —
|
|
4
|
+
chat verbosity and document language, nothing else. Missing file → defaults below.
|
|
5
|
+
Created/edited via `/oris-flow` → Setup, or by hand.
|
|
5
6
|
|
|
6
7
|
## Shape (v1)
|
|
7
8
|
|
|
8
9
|
```json
|
|
9
10
|
{
|
|
10
11
|
"version": 1,
|
|
11
|
-
"orisFlow": { "
|
|
12
|
-
"defaultProfiles": { "testing": "default" },
|
|
13
|
-
"profiles": {
|
|
14
|
-
"default": {
|
|
15
|
-
"environment": "local",
|
|
16
|
-
"values": { "baseUrl": "" },
|
|
17
|
-
"secrets": { "username": "", "password": "" },
|
|
18
|
-
"flags": { "allowDataMutation": false }
|
|
19
|
-
}
|
|
20
|
-
}
|
|
12
|
+
"orisFlow": { "responseStyle": "standard", "artifactLanguage": "en" }
|
|
21
13
|
}
|
|
22
14
|
```
|
|
23
15
|
|
|
24
|
-
- `uiLanguage` → chat, menus, question labels, recaps (`en` | `it`, default `en`).
|
|
25
|
-
- `artifactLanguage` → generated documents (`en` | `it`, default `en`).
|
|
26
16
|
- `responseStyle` → chat verbosity (`standard` | `caveman`, default `standard`). Caveman = maximum token density in chat; contract in `references/conventions.md` `## Response style`. Documents and gates are never compressed.
|
|
27
|
-
- `
|
|
28
|
-
- `flags.allowDataMutation` → data mutation allowed ONLY with `environment: local` + loopback URLs + this flag true.
|
|
17
|
+
- `artifactLanguage` → language for generated documents (ISO code, default `en`). Chat/UI language is NOT configured here — it is deduced from the conversation.
|
|
29
18
|
|
|
30
19
|
## Rules
|
|
31
20
|
|
|
32
|
-
1. READ settings before the first workflow
|
|
21
|
+
1. READ settings before the first workflow decision; missing/invalid → defaults above.
|
|
33
22
|
2. Current user instructions override settings for the turn.
|
|
34
|
-
3.
|
|
35
|
-
4. CONFIRM before writing settings. Update via `/oris-flow` → Setup
|
|
36
|
-
5. In an approved loop scope, use only the profile/commands/tools the loop declares; otherwise `blocked`.
|
|
23
|
+
3. Oris stores no credentials. Test/CI secrets belong to each project's own tooling — never Oris files, docs, loop state, receipts, or logs.
|
|
24
|
+
4. CONFIRM before writing settings. Update via `/oris-flow` → Setup.
|
|
@@ -1,80 +1,30 @@
|
|
|
1
|
-
{
|
|
2
|
-
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
-
"title": "Oris Flow Settings",
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
"required": ["version", "orisFlow"
|
|
7
|
-
"additionalProperties": false,
|
|
8
|
-
"properties": {
|
|
9
|
-
"version": {
|
|
10
|
-
"type": "integer",
|
|
11
|
-
"const": 1
|
|
12
|
-
},
|
|
13
|
-
"orisFlow": {
|
|
14
|
-
"type": "object",
|
|
15
|
-
"
|
|
16
|
-
"
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
},
|
|
22
|
-
"artifactLanguage": {
|
|
23
|
-
"type": "string",
|
|
24
|
-
"
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
}
|
|
32
|
-
},
|
|
33
|
-
"defaultProfiles": {
|
|
34
|
-
"type": "object",
|
|
35
|
-
"minProperties": 1,
|
|
36
|
-
"additionalProperties": {
|
|
37
|
-
"type": "string",
|
|
38
|
-
"minLength": 1
|
|
39
|
-
}
|
|
40
|
-
},
|
|
41
|
-
"profiles": {
|
|
42
|
-
"type": "object",
|
|
43
|
-
"minProperties": 1,
|
|
44
|
-
"additionalProperties": {
|
|
45
|
-
"$ref": "#/$defs/profile"
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
},
|
|
49
|
-
"$defs": {
|
|
50
|
-
"profile": {
|
|
51
|
-
"type": "object",
|
|
52
|
-
"additionalProperties": false,
|
|
53
|
-
"required": ["environment", "values", "secrets", "flags"],
|
|
54
|
-
"properties": {
|
|
55
|
-
"environment": {
|
|
56
|
-
"type": "string",
|
|
57
|
-
"minLength": 1
|
|
58
|
-
},
|
|
59
|
-
"values": {
|
|
60
|
-
"type": "object",
|
|
61
|
-
"additionalProperties": {
|
|
62
|
-
"type": ["string", "number", "boolean"]
|
|
63
|
-
}
|
|
64
|
-
},
|
|
65
|
-
"secrets": {
|
|
66
|
-
"type": "object",
|
|
67
|
-
"additionalProperties": {
|
|
68
|
-
"type": "string"
|
|
69
|
-
}
|
|
70
|
-
},
|
|
71
|
-
"flags": {
|
|
72
|
-
"type": "object",
|
|
73
|
-
"additionalProperties": {
|
|
74
|
-
"type": "boolean"
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"title": "Oris Flow Settings",
|
|
4
|
+
"$id": "https://oris.dev/schemas/oris-flow-settings-v1.json",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"required": ["version", "orisFlow"],
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"properties": {
|
|
9
|
+
"version": {
|
|
10
|
+
"type": "integer",
|
|
11
|
+
"const": 1
|
|
12
|
+
},
|
|
13
|
+
"orisFlow": {
|
|
14
|
+
"type": "object",
|
|
15
|
+
"additionalProperties": false,
|
|
16
|
+
"properties": {
|
|
17
|
+
"responseStyle": {
|
|
18
|
+
"type": "string",
|
|
19
|
+
"enum": ["standard", "caveman"],
|
|
20
|
+
"description": "Chat verbosity: standard prose or caveman (maximum token density; documents and gates never compressed). Default standard."
|
|
21
|
+
},
|
|
22
|
+
"artifactLanguage": {
|
|
23
|
+
"type": "string",
|
|
24
|
+
"minLength": 2,
|
|
25
|
+
"description": "Language for generated documents (ISO code, e.g. en, it). Default en. Chat language is deduced from the conversation, not configured here."
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -8,9 +8,9 @@ export const ORIS_FLOW_TASKS_ROOT = ".oris-flow/tasks";
|
|
|
8
8
|
export const ORIS_FLOW_ADAPTER = ".oris-flow/adapter.json";
|
|
9
9
|
export const ORIS_FLOW_LOOPS_ROOT = ".oris-flow/loops";
|
|
10
10
|
export const ORIS_FLOW_RUNTIME = ".oris-flow/runtime";
|
|
11
|
+
export const ORIS_FLOW_SETTINGS = ".oris-flow/settings.json";
|
|
11
12
|
export const ORIS_HOME = path.join(os.homedir(), ".oris");
|
|
12
13
|
export const ORIS_BUNDLE_ROOT = path.join(ORIS_HOME, "oris-skills");
|
|
13
|
-
export const ORIS_SKILLS_SETTINGS = path.join(ORIS_HOME, "settings.json");
|
|
14
14
|
export const ORIS_LOOP_SCHEMA = "https://oris.dev/schemas/oris-loop-adapter-v1.json";
|
|
15
15
|
export const ORIS_LOOP_SCHEMA_VERSION = 1;
|
|
16
16
|
export const ORIS_LOOP_DOCUMENT_VERSION = 2;
|
|
@@ -433,9 +433,9 @@ function resolveTasksRoot(root, existingManifest) {
|
|
|
433
433
|
|
|
434
434
|
/**
|
|
435
435
|
* Refresh keeps user truth: confirmed decisions, tasksRoot, version control,
|
|
436
|
-
*
|
|
437
|
-
*
|
|
438
|
-
*
|
|
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
439
|
*/
|
|
440
440
|
function mergeWithExisting(scan, existingManifest) {
|
|
441
441
|
if (!existingManifest || typeof existingManifest !== "object") return scan;
|
|
@@ -447,9 +447,6 @@ function mergeWithExisting(scan, existingManifest) {
|
|
|
447
447
|
if (existingManifest.setup?.versionControl?.orisFlowMap) {
|
|
448
448
|
merged.manifest.setup.versionControl.orisFlowMap = existingManifest.setup.versionControl.orisFlowMap;
|
|
449
449
|
}
|
|
450
|
-
if (existingManifest.refreshPolicy && typeof existingManifest.refreshPolicy === "object") {
|
|
451
|
-
merged.manifest.refreshPolicy = existingManifest.refreshPolicy;
|
|
452
|
-
}
|
|
453
450
|
if (existingManifest.projectType?.confidence === "confirmed") {
|
|
454
451
|
merged.manifest.projectType = existingManifest.projectType;
|
|
455
452
|
}
|
|
@@ -507,9 +504,6 @@ export function scanRepository(repositoryRoot, options = {}) {
|
|
|
507
504
|
orisFlowMap: options.versionControl ?? "commit",
|
|
508
505
|
},
|
|
509
506
|
},
|
|
510
|
-
refreshPolicy: {
|
|
511
|
-
mode: "manual",
|
|
512
|
-
},
|
|
513
507
|
stacks,
|
|
514
508
|
commands,
|
|
515
509
|
sections: sectionPointers,
|
|
@@ -47,7 +47,6 @@ function defaultPaths() {
|
|
|
47
47
|
const home = os.homedir();
|
|
48
48
|
return {
|
|
49
49
|
bundleRoot: path.join(home, ".oris", "oris-skills"),
|
|
50
|
-
settingsPath: path.join(home, ".oris", "settings.json"),
|
|
51
50
|
agentRoots: {
|
|
52
51
|
cursor: path.join(home, ".cursor"),
|
|
53
52
|
claude: path.join(home, ".claude"),
|
|
@@ -64,7 +63,6 @@ function parseArgs(argv) {
|
|
|
64
63
|
force: false,
|
|
65
64
|
agents: [],
|
|
66
65
|
bundleRoot: paths.bundleRoot,
|
|
67
|
-
settingsPath: paths.settingsPath,
|
|
68
66
|
agentRoots: paths.agentRoots,
|
|
69
67
|
};
|
|
70
68
|
for (let i = 0; i < argv.length; i += 1) {
|
|
@@ -81,7 +79,6 @@ function parseArgs(argv) {
|
|
|
81
79
|
else if (key === "--agents" || key.startsWith("--agents=")) {
|
|
82
80
|
options.agents = value().split(",").map((name) => name.trim().toLowerCase()).filter(Boolean);
|
|
83
81
|
} else if (key === "--bundle-root" || key.startsWith("--bundle-root=")) options.bundleRoot = value();
|
|
84
|
-
else if (key === "--settings" || key.startsWith("--settings=")) options.settingsPath = value();
|
|
85
82
|
else if (key === "--cursor-root" || key.startsWith("--cursor-root=")) options.agentRoots.cursor = value();
|
|
86
83
|
else if (key === "--claude-root" || key.startsWith("--claude-root=")) options.agentRoots.claude = value();
|
|
87
84
|
else if (key === "--codex-root" || key.startsWith("--codex-root=")) options.agentRoots.codex = value();
|
|
@@ -160,28 +157,24 @@ function manifestSkills(manifest, repoRoot) {
|
|
|
160
157
|
});
|
|
161
158
|
}
|
|
162
159
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
},
|
|
182
|
-
},
|
|
183
|
-
},
|
|
184
|
-
};
|
|
160
|
+
/**
|
|
161
|
+
* Rewrite bundle-root-relative Oris paths to skill-root-relative in a standalone skill
|
|
162
|
+
* copy. The source keeps bundle-relative paths (`skills/<name>/references/…`) so the bundle
|
|
163
|
+
* and the Codex/Copilot pointers resolve; a standalone Claude/Cursor skill has no bundle
|
|
164
|
+
* root above it, so its own `.md` files must point at `references/…` and `templates/…`.
|
|
165
|
+
*/
|
|
166
|
+
function rewriteBundlePaths(skillDir, relativePath) {
|
|
167
|
+
// In a standalone skill the skill folder IS the root, so any `skills/<name>/…` pointer
|
|
168
|
+
// (SKILL.md, references/…, templates/…, agents/…) becomes root-relative once the prefix
|
|
169
|
+
// is stripped.
|
|
170
|
+
const prefix = `${relativePath.replace(/\\/g, "/").replace(/\/+$/, "")}/`;
|
|
171
|
+
const entries = fs.readdirSync(skillDir, { recursive: true, withFileTypes: true });
|
|
172
|
+
for (const entry of entries) {
|
|
173
|
+
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
|
|
174
|
+
const filePath = path.join(entry.parentPath ?? entry.path, entry.name);
|
|
175
|
+
const text = fs.readFileSync(filePath, "utf8");
|
|
176
|
+
if (text.includes(prefix)) fs.writeFileSync(filePath, text.split(prefix).join(""), "utf8");
|
|
177
|
+
}
|
|
185
178
|
}
|
|
186
179
|
|
|
187
180
|
function installSkillDirs(skills, skillsRoot, manifest, bundleRoot, options) {
|
|
@@ -193,6 +186,13 @@ function installSkillDirs(skills, skillsRoot, manifest, bundleRoot, options) {
|
|
|
193
186
|
}
|
|
194
187
|
fs.rmSync(target, { recursive: true, force: true });
|
|
195
188
|
copy(skill.sourceDir, target);
|
|
189
|
+
// Make the standalone skill self-contained: the shared references it points to
|
|
190
|
+
// (conventions.md, doc-policy.md, …) live in the bundle root, not inside the skill
|
|
191
|
+
// folder, so they were never copied — the whole reason `references/conventions.md`
|
|
192
|
+
// dangled at runtime. Merge them in, then rewrite the paths to skill-relative.
|
|
193
|
+
const bundleReferences = path.join(bundleRoot, "references");
|
|
194
|
+
if (fs.existsSync(bundleReferences)) copy(bundleReferences, path.join(target, "references"));
|
|
195
|
+
rewriteBundlePaths(target, skill.relativePath);
|
|
196
196
|
writeJson(path.join(target, markerName), {
|
|
197
197
|
package: manifest.name,
|
|
198
198
|
version: manifest.version,
|
|
@@ -262,7 +262,6 @@ function main() {
|
|
|
262
262
|
}
|
|
263
263
|
|
|
264
264
|
assertInsideHome(options.bundleRoot);
|
|
265
|
-
assertInsideHome(options.settingsPath);
|
|
266
265
|
for (const agent of agents) assertInsideHome(options.agentRoots[agent]);
|
|
267
266
|
|
|
268
267
|
if (options.dryRun) {
|
|
@@ -274,7 +273,6 @@ function main() {
|
|
|
274
273
|
else log(`Would write ${agent} skills: ${path.join(options.agentRoots[agent], "skills")}`);
|
|
275
274
|
}
|
|
276
275
|
log(`Would write universal pointer: ${path.join(path.dirname(options.bundleRoot), "oris-flow.md")}`);
|
|
277
|
-
log(`Would create settings if missing: ${options.settingsPath}`);
|
|
278
276
|
return;
|
|
279
277
|
}
|
|
280
278
|
|
|
@@ -303,7 +301,6 @@ function main() {
|
|
|
303
301
|
}
|
|
304
302
|
installUniversalPointer(skills, options.bundleRoot);
|
|
305
303
|
|
|
306
|
-
if (!fs.existsSync(options.settingsPath)) writeJson(options.settingsPath, defaultSettings());
|
|
307
304
|
writeJson(path.join(options.bundleRoot, installManifestName), {
|
|
308
305
|
package: manifest.name,
|
|
309
306
|
version: manifest.version,
|
|
@@ -15,7 +15,6 @@ import {
|
|
|
15
15
|
ORIS_FLOW_ADAPTER,
|
|
16
16
|
ORIS_LOOP_SCHEMA,
|
|
17
17
|
ORIS_LOOP_SCHEMA_VERSION,
|
|
18
|
-
ORIS_SKILLS_SETTINGS,
|
|
19
18
|
} from "../flow/oris-flow-layout.mjs";
|
|
20
19
|
import {
|
|
21
20
|
activeRoles,
|
|
@@ -42,8 +41,6 @@ function parseArgs(argv) {
|
|
|
42
41
|
runner: "chat",
|
|
43
42
|
maxIterations: 10,
|
|
44
43
|
maxMinutes: 240,
|
|
45
|
-
profile: "",
|
|
46
|
-
settingsPath: ORIS_SKILLS_SETTINGS,
|
|
47
44
|
};
|
|
48
45
|
const readValue = (index, raw) => {
|
|
49
46
|
if (raw.includes("=")) return [index, raw.slice(raw.indexOf("=") + 1)];
|
|
@@ -60,8 +57,6 @@ function parseArgs(argv) {
|
|
|
60
57
|
else if (key === "--progress" || key.startsWith("--progress=")) [i, options.progress] = readValue(i, raw);
|
|
61
58
|
else if (key === "--repository-root" || key.startsWith("--repository-root=")) [i, options.repositoryRoot] = readValue(i, raw);
|
|
62
59
|
else if (key === "--runner" || key.startsWith("--runner=")) [i, options.runner] = readValue(i, raw);
|
|
63
|
-
else if (key === "--profile" || key.startsWith("--profile=")) [i, options.profile] = readValue(i, raw);
|
|
64
|
-
else if (key === "--settings-path" || key.startsWith("--settings-path=")) [i, options.settingsPath] = readValue(i, raw);
|
|
65
60
|
else if (key === "--max-iterations" || key.startsWith("--max-iterations=")) {
|
|
66
61
|
const parsed = readValue(i, raw);
|
|
67
62
|
i = parsed[0];
|
|
@@ -143,30 +138,6 @@ function reconcileContext(root, state) {
|
|
|
143
138
|
fs.renameSync(temporary, contextPath);
|
|
144
139
|
}
|
|
145
140
|
|
|
146
|
-
function getPathValue(value, dottedPath) {
|
|
147
|
-
return dottedPath.split(".").filter(Boolean).reduce((current, key) => (
|
|
148
|
-
current != null && typeof current === "object" ? current[key] : undefined
|
|
149
|
-
), value);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
function validateRequiredProfile(options, adapter) {
|
|
153
|
-
if (adapter.profile?.required !== true) return;
|
|
154
|
-
const notArmed = "Oris chat loop was not armed; the stop hook will not resume this chat. Fix the profile, rerun start, then end the turn.";
|
|
155
|
-
if (!fs.existsSync(options.settingsPath)) fail(`Required Oris settings are missing: ${options.settingsPath}. ${notArmed}`);
|
|
156
|
-
const settings = JSON.parse(fs.readFileSync(options.settingsPath, "utf8"));
|
|
157
|
-
if (settings.version !== 1) fail(`Oris settings must use schema v1. ${notArmed}`);
|
|
158
|
-
const role = adapter.profile.defaultRole || "testing";
|
|
159
|
-
const profileName = options.profile || settings.defaultProfiles?.[role];
|
|
160
|
-
const profile = settings.profiles?.[profileName];
|
|
161
|
-
if (!profile) fail(`Required Oris profile '${profileName || "(none)"}' was not found. ${notArmed}`);
|
|
162
|
-
for (const mapping of adapter.profile.mappings ?? []) {
|
|
163
|
-
const value = getPathValue(profile, mapping.from);
|
|
164
|
-
if (mapping.required === true && (value == null || String(value).trim() === "")) {
|
|
165
|
-
fail(`Profile '${profileName}' is missing required field '${mapping.from}'. ${notArmed}`);
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
141
|
/** A same-chat loop needs at least one armed stop hook: Cursor or Claude Code. */
|
|
171
142
|
function assessStopHooks(root) {
|
|
172
143
|
const registered = [];
|
|
@@ -267,7 +238,6 @@ if (options.action === "start") {
|
|
|
267
238
|
fail("No Oris stop hook is registered (.cursor/hooks.json or .claude/settings.json). Run: oris-skills loop bootstrap");
|
|
268
239
|
}
|
|
269
240
|
}
|
|
270
|
-
validateRequiredProfile(options, adapter);
|
|
271
241
|
const minimumFreeDiskGiB = Number(adapter.preflight?.minFreeDiskGiB ?? 0);
|
|
272
242
|
if (typeof fs.statfsSync === "function") {
|
|
273
243
|
const disk = fs.statfsSync(root);
|
|
@@ -11,6 +11,7 @@ const nodeTestFiles = [
|
|
|
11
11
|
"scripts/tests/test-schemas.mjs",
|
|
12
12
|
"scripts/tests/test-oris-loop-document.mjs",
|
|
13
13
|
"scripts/tests/test-agent-adapters.mjs",
|
|
14
|
+
"scripts/tests/test-skill-self-contained.mjs",
|
|
14
15
|
"scripts/tests/test-oris-1-0-cleanliness.mjs",
|
|
15
16
|
"scripts/tests/test-oris-flow-scan.mjs",
|
|
16
17
|
"scripts/tests/test-routing-lifecycle.mjs",
|
|
@@ -6,7 +6,7 @@ import path from "node:path";
|
|
|
6
6
|
import test from "node:test";
|
|
7
7
|
import { fileURLToPath } from "node:url";
|
|
8
8
|
import { initSmokeRepository } from "../loop/oris-loop-fixtures.mjs";
|
|
9
|
-
import {
|
|
9
|
+
import { ORIS_FLOW_RUNTIME } from "../flow/oris-flow-layout.mjs";
|
|
10
10
|
|
|
11
11
|
const scriptsRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "loop");
|
|
12
12
|
|
|
@@ -118,46 +118,3 @@ test("same-chat start accepts an existing loop slug", () => {
|
|
|
118
118
|
}
|
|
119
119
|
});
|
|
120
120
|
|
|
121
|
-
test("missing required profile leaves same-chat loop unarmed", () => {
|
|
122
|
-
const root = fs.mkdtempSync(path.join(os.tmpdir(), "oris-loop-profile-"));
|
|
123
|
-
try {
|
|
124
|
-
initSmokeRepository(root, {
|
|
125
|
-
taskSlug: "profile-smoke",
|
|
126
|
-
program: "profile-smoke",
|
|
127
|
-
});
|
|
128
|
-
const adapterPath = path.join(root, ORIS_FLOW_ADAPTER);
|
|
129
|
-
const adapter = JSON.parse(fs.readFileSync(adapterPath, "utf8"));
|
|
130
|
-
adapter.profile = {
|
|
131
|
-
required: true,
|
|
132
|
-
defaultRole: "testing",
|
|
133
|
-
mappings: [
|
|
134
|
-
{ env: "BASE_URL", from: "values.baseUrl", required: true, secret: false },
|
|
135
|
-
],
|
|
136
|
-
};
|
|
137
|
-
fs.writeFileSync(adapterPath, `${JSON.stringify(adapter, null, 2)}\n`, "utf8");
|
|
138
|
-
const settingsPath = path.join(root, "settings.json");
|
|
139
|
-
fs.writeFileSync(settingsPath, `${JSON.stringify({
|
|
140
|
-
version: 1,
|
|
141
|
-
orisFlow: { uiLanguage: "en", artifactLanguage: "en" },
|
|
142
|
-
defaultProfiles: { testing: "default" },
|
|
143
|
-
profiles: {
|
|
144
|
-
default: { environment: "local", values: {}, secrets: {}, flags: {} },
|
|
145
|
-
},
|
|
146
|
-
}, null, 2)}\n`, "utf8");
|
|
147
|
-
|
|
148
|
-
const chat = path.join(scriptsRoot, "oris-loop-chat.mjs");
|
|
149
|
-
const start = spawnSync(process.execPath, [
|
|
150
|
-
chat,
|
|
151
|
-
"--action", "start",
|
|
152
|
-
"--loop", "profile-smoke",
|
|
153
|
-
"--repository-root", root,
|
|
154
|
-
"--settings-path", settingsPath,
|
|
155
|
-
], { encoding: "utf8" });
|
|
156
|
-
assert.notEqual(start.status, 0);
|
|
157
|
-
assert.match(start.stderr, /missing required field 'values\.baseUrl'/);
|
|
158
|
-
assert.match(start.stderr, /Oris chat loop was not armed; the stop hook will not resume this chat/);
|
|
159
|
-
assert.equal(fs.existsSync(path.join(root, ORIS_FLOW_RUNTIME, "chat-active.json")), false);
|
|
160
|
-
} finally {
|
|
161
|
-
fs.rmSync(root, { recursive: true, force: true });
|
|
162
|
-
}
|
|
163
|
-
});
|
|
@@ -12,26 +12,27 @@ function read(relativePath) {
|
|
|
12
12
|
return JSON.parse(fs.readFileSync(path.join(root, relativePath), "utf8"));
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
test("settings schema is the
|
|
15
|
+
test("settings schema is the minimal v1 contract: only responseStyle and artifactLanguage", () => {
|
|
16
16
|
const schema = read("references/settings.schema.json");
|
|
17
17
|
assert.equal(schema.properties.version.const, 1);
|
|
18
|
-
assert.deepEqual(schema.required, ["version", "orisFlow"
|
|
19
|
-
assert.ok(schema
|
|
20
|
-
assert.ok(schema
|
|
21
|
-
assert.ok(schema
|
|
18
|
+
assert.deepEqual(schema.required, ["version", "orisFlow"]);
|
|
19
|
+
assert.ok(schema.properties.orisFlow.properties.responseStyle);
|
|
20
|
+
assert.ok(schema.properties.orisFlow.properties.artifactLanguage);
|
|
21
|
+
assert.ok(!schema.properties.defaultProfiles, "test profiles were removed from settings");
|
|
22
|
+
assert.ok(!schema.properties.profiles, "test profiles were removed from settings");
|
|
22
23
|
});
|
|
23
24
|
|
|
24
|
-
test("adapter schema v1 declares paths, commands, model aliases,
|
|
25
|
+
test("adapter schema v1 declares paths, commands, model aliases, preflight (no test profiles)", () => {
|
|
25
26
|
const schema = read("references/loop-adapter.schema.json");
|
|
26
27
|
assert.equal(schema.$id, "https://oris.dev/schemas/oris-loop-adapter-v1.json");
|
|
27
28
|
assert.equal(schema.properties.schemaVersion.const, 1);
|
|
28
29
|
assert.deepEqual(
|
|
29
30
|
schema.required,
|
|
30
|
-
["$schema", "schemaVersion", "repository", "paths", "commands", "
|
|
31
|
+
["$schema", "schemaVersion", "repository", "paths", "commands", "preflight"],
|
|
31
32
|
);
|
|
32
33
|
assert.ok(schema.properties.paths.properties.loops);
|
|
33
34
|
assert.ok(schema.properties.models, "adapter schema must allow a model alias map");
|
|
34
|
-
assert.
|
|
35
|
+
assert.ok(!schema.properties.profile, "test profiles were removed from the adapter");
|
|
35
36
|
});
|
|
36
37
|
|
|
37
38
|
test("loop document schema is v2 with approved gate, per-role models, and improve mode", () => {
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import childProcess from "node:child_process";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import assert from "node:assert/strict";
|
|
6
|
+
import process from "node:process";
|
|
7
|
+
import test from "node:test";
|
|
8
|
+
import { fileURLToPath } from "node:url";
|
|
9
|
+
|
|
10
|
+
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..");
|
|
11
|
+
|
|
12
|
+
function runCli(args, homeDir) {
|
|
13
|
+
return childProcess.spawnSync(process.execPath, ["scripts/oris-skills.mjs", ...args], {
|
|
14
|
+
cwd: root,
|
|
15
|
+
encoding: "utf8",
|
|
16
|
+
env: { ...process.env, HOME: homeDir, USERPROFILE: homeDir },
|
|
17
|
+
shell: false,
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function markdownFiles(dir) {
|
|
22
|
+
return fs
|
|
23
|
+
.readdirSync(dir, { recursive: true, withFileTypes: true })
|
|
24
|
+
.filter((entry) => entry.isFile() && entry.name.endsWith(".md"))
|
|
25
|
+
.map((entry) => path.join(entry.parentPath ?? entry.path, entry.name));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// A standalone skill (Claude/Cursor) has no bundle root above it, so every reference it
|
|
29
|
+
// points to must resolve inside the skill folder — the regression that broke
|
|
30
|
+
// `references/conventions.md` at runtime.
|
|
31
|
+
test("installed Claude skill is self-contained", () => {
|
|
32
|
+
const homeDir = fs.mkdtempSync(path.join(os.tmpdir(), "oris-self-contained-"));
|
|
33
|
+
try {
|
|
34
|
+
fs.mkdirSync(path.join(homeDir, ".claude"), { recursive: true });
|
|
35
|
+
const result = runCli(["install", "--force"], homeDir);
|
|
36
|
+
assert.equal(result.status, 0, result.stderr);
|
|
37
|
+
|
|
38
|
+
const skill = path.join(homeDir, ".claude", "skills", "oris-flow");
|
|
39
|
+
assert.ok(fs.existsSync(skill), "oris-flow skill was installed");
|
|
40
|
+
|
|
41
|
+
// The shared references that used to dangle must now be present.
|
|
42
|
+
for (const name of ["conventions.md", "doc-policy.md"]) {
|
|
43
|
+
assert.ok(
|
|
44
|
+
fs.existsSync(path.join(skill, "references", name)),
|
|
45
|
+
`shared reference ${name} is bundled into the skill`,
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const files = markdownFiles(skill);
|
|
50
|
+
const pathToken = /(?:references|templates|agents)\/[A-Za-z0-9._/-]+\.(?:md|json|toml)/g;
|
|
51
|
+
const missing = [];
|
|
52
|
+
let bundlePrefixLeaks = 0;
|
|
53
|
+
for (const file of files) {
|
|
54
|
+
const text = fs.readFileSync(file, "utf8");
|
|
55
|
+
// No bundle-root-relative pointer may survive in a standalone skill.
|
|
56
|
+
if (text.includes("skills/oris-flow/")) bundlePrefixLeaks += 1;
|
|
57
|
+
for (const cited of text.match(pathToken) ?? []) {
|
|
58
|
+
if (!fs.existsSync(path.join(skill, cited))) missing.push(`${path.basename(file)} → ${cited}`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
assert.equal(bundlePrefixLeaks, 0, "no `skills/oris-flow/` paths remain in the standalone skill");
|
|
62
|
+
assert.deepEqual(missing, [], "every cited reference resolves inside the skill");
|
|
63
|
+
} finally {
|
|
64
|
+
fs.rmSync(homeDir, { recursive: true, force: true });
|
|
65
|
+
}
|
|
66
|
+
});
|
|
@@ -50,7 +50,9 @@ Match the desired outcome to ONE route, then apply its reference:
|
|
|
50
50
|
|
|
51
51
|
## Menu (low confidence)
|
|
52
52
|
|
|
53
|
-
ASK "What do you want to do?"
|
|
53
|
+
ASK "What do you want to do?" in the user's language. This menu exceeds every
|
|
54
|
+
question tool's option cap — present it as a plain-text lettered list (the deliberate
|
|
55
|
+
fallback in `references/conventions.md` `## Questions`), options:
|
|
54
56
|
|
|
55
57
|
- Setup repository
|
|
56
58
|
- New project from scratch
|
|
@@ -16,7 +16,7 @@ details on request.
|
|
|
16
16
|
| Loop status | `npx oris-skills loop chat --action status` / `npx oris-skills loop list` |
|
|
17
17
|
| Skill missing after install | Reload the agent; check `~/.oris/oris-skills/` exists; re-run install with `--force`. |
|
|
18
18
|
| Hook not firing | `npx oris-skills loop verify --temp` self-checks the runtime; `npx oris-skills loop bootstrap` re-arms repo hooks. |
|
|
19
|
-
| Change language /
|
|
19
|
+
| Change document language / chat verbosity | `/oris-flow` → Setup (settings live in `.oris-flow/settings.json`). |
|
|
20
20
|
| Ultra-short answers | `/oris-flow` → Setup → set `responseStyle: caveman` — chat shrinks to maximum token density; documents stay complete. |
|
|
21
21
|
| Edit a loop's behavior | Edit the files in `.oris-flow/loops/{slug}/prompts/` — the next pass obeys them. |
|
|
22
22
|
|
|
@@ -18,7 +18,7 @@ refreshing. THEN ask ONE question with these options:
|
|
|
18
18
|
- review stale sections
|
|
19
19
|
- show summary (no writes)
|
|
20
20
|
- recreate from scratch
|
|
21
|
-
-
|
|
21
|
+
- preferences → Local Preferences below; touches only `.oris-flow/settings.json`
|
|
22
22
|
- clean runtime → preview with `npx oris-skills flow clean-runtime --repository-root <repo> --dry-run`, confirm, run without `--dry-run`
|
|
23
23
|
|
|
24
24
|
ELSE → create from scratch.
|
|
@@ -29,9 +29,13 @@ ELSE → create from scratch.
|
|
|
29
29
|
2. SCAN: `npx oris-skills flow scan --repository-root <repo> --json` — deterministic
|
|
30
30
|
baseline: `projectType`, stacks, commands (with confidence), sections, `truncated`
|
|
31
31
|
flag. Scan output = draft evidence, not truth.
|
|
32
|
-
3. IF `truncated: true` → say so
|
|
33
|
-
4.
|
|
34
|
-
skills/rules)
|
|
32
|
+
3. IF `truncated: true` → say so, then per-area subagent scans are REQUIRED, not optional.
|
|
33
|
+
4. FAN OUT parallel subagent scans (docs, frontend, backend/data, tests/build, local
|
|
34
|
+
skills/rules) by DEFAULT whenever the repo has more than one of those areas —
|
|
35
|
+
entering setup is the user's request to spawn (`references/conventions.md`
|
|
36
|
+
`## Subagents`). SKIP only when the repo is small enough that the baseline already
|
|
37
|
+
answers every §2 decision — say you skipped and why. No agent tool on the platform →
|
|
38
|
+
same scans inline, sequentially.
|
|
35
39
|
|
|
36
40
|
## 2. Present findings, decide one at a time
|
|
37
41
|
|
|
@@ -45,8 +49,6 @@ Then WALK decisions ONE per turn — short explainer first, recommended answer f
|
|
|
45
49
|
project docs. Record in `setup.tasksRoot`.
|
|
46
50
|
- **Verify commands** — show detected build+test commands; user confirms or corrects →
|
|
47
51
|
mark `confirmed`. They feed implement, verify, and loop VERIFIER; unconfirmed = a guess.
|
|
48
|
-
- **Refresh policy** (manual | monthly | after refactor | after test-layout change) —
|
|
49
|
-
advisory only, never a scheduler.
|
|
50
52
|
- **Version control for `.oris-flow/`** — commit (team default) or gitignore. Task docs
|
|
51
53
|
follow the same choice.
|
|
52
54
|
|
|
@@ -78,10 +80,10 @@ ON explicit confirmation per file → scaffold the missing ones minimal.
|
|
|
78
80
|
|
|
79
81
|
## Local Preferences
|
|
80
82
|
|
|
81
|
-
1. READ
|
|
82
|
-
2. ASK what to change:
|
|
83
|
-
caveman — maximum token density in chat)
|
|
84
|
-
3. SHOW
|
|
83
|
+
1. READ `.oris-flow/settings.json` (defaults in `references/settings.md`).
|
|
84
|
+
2. ASK what to change: artifactLanguage (document language, default `en`) | responseStyle
|
|
85
|
+
(standard | caveman — maximum token density in chat).
|
|
86
|
+
3. SHOW summary, CONFIRM, write ONLY the settings file.
|
|
85
87
|
|
|
86
88
|
## Never
|
|
87
89
|
|