arggon-harness 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (170) hide show
  1. package/README.md +229 -0
  2. package/config/default-opencode.json +21 -0
  3. package/dist/init.d.ts +3 -0
  4. package/dist/init.d.ts.map +1 -0
  5. package/dist/init.js +406 -0
  6. package/dist/init.js.map +1 -0
  7. package/dist/plugin/engine/artifact-graph.d.ts +38 -0
  8. package/dist/plugin/engine/artifact-graph.d.ts.map +1 -0
  9. package/dist/plugin/engine/artifact-graph.js +137 -0
  10. package/dist/plugin/engine/artifact-graph.js.map +1 -0
  11. package/dist/plugin/engine/config.d.ts +21 -0
  12. package/dist/plugin/engine/config.d.ts.map +1 -0
  13. package/dist/plugin/engine/config.js +146 -0
  14. package/dist/plugin/engine/config.js.map +1 -0
  15. package/dist/plugin/engine/delta-apply.d.ts +23 -0
  16. package/dist/plugin/engine/delta-apply.d.ts.map +1 -0
  17. package/dist/plugin/engine/delta-apply.js +153 -0
  18. package/dist/plugin/engine/delta-apply.js.map +1 -0
  19. package/dist/plugin/engine/git.d.ts +32 -0
  20. package/dist/plugin/engine/git.d.ts.map +1 -0
  21. package/dist/plugin/engine/git.js +61 -0
  22. package/dist/plugin/engine/git.js.map +1 -0
  23. package/dist/plugin/engine/integrity.d.ts +45 -0
  24. package/dist/plugin/engine/integrity.d.ts.map +1 -0
  25. package/dist/plugin/engine/integrity.js +98 -0
  26. package/dist/plugin/engine/integrity.js.map +1 -0
  27. package/dist/plugin/engine/registry.d.ts +39 -0
  28. package/dist/plugin/engine/registry.d.ts.map +1 -0
  29. package/dist/plugin/engine/registry.js +191 -0
  30. package/dist/plugin/engine/registry.js.map +1 -0
  31. package/dist/plugin/engine/state.d.ts +31 -0
  32. package/dist/plugin/engine/state.d.ts.map +1 -0
  33. package/dist/plugin/engine/state.js +116 -0
  34. package/dist/plugin/engine/state.js.map +1 -0
  35. package/dist/plugin/engine/task-tracker.d.ts +66 -0
  36. package/dist/plugin/engine/task-tracker.d.ts.map +1 -0
  37. package/dist/plugin/engine/task-tracker.js +124 -0
  38. package/dist/plugin/engine/task-tracker.js.map +1 -0
  39. package/dist/plugin/engine/types.d.ts +349 -0
  40. package/dist/plugin/engine/types.d.ts.map +1 -0
  41. package/dist/plugin/engine/types.js +3 -0
  42. package/dist/plugin/engine/types.js.map +1 -0
  43. package/dist/plugin/engine/validator.d.ts +52 -0
  44. package/dist/plugin/engine/validator.d.ts.map +1 -0
  45. package/dist/plugin/engine/validator.js +457 -0
  46. package/dist/plugin/engine/validator.js.map +1 -0
  47. package/dist/plugin/engine/verifier.d.ts +61 -0
  48. package/dist/plugin/engine/verifier.d.ts.map +1 -0
  49. package/dist/plugin/engine/verifier.js +441 -0
  50. package/dist/plugin/engine/verifier.js.map +1 -0
  51. package/dist/plugin/hooks/context-injection.d.ts +11 -0
  52. package/dist/plugin/hooks/context-injection.d.ts.map +1 -0
  53. package/dist/plugin/hooks/context-injection.js +88 -0
  54. package/dist/plugin/hooks/context-injection.js.map +1 -0
  55. package/dist/plugin/hooks/event-handlers.d.ts +9 -0
  56. package/dist/plugin/hooks/event-handlers.d.ts.map +1 -0
  57. package/dist/plugin/hooks/event-handlers.js +10 -0
  58. package/dist/plugin/hooks/event-handlers.js.map +1 -0
  59. package/dist/plugin/hooks/workflow-gate.d.ts +24 -0
  60. package/dist/plugin/hooks/workflow-gate.d.ts.map +1 -0
  61. package/dist/plugin/hooks/workflow-gate.js +78 -0
  62. package/dist/plugin/hooks/workflow-gate.js.map +1 -0
  63. package/dist/plugin/index.d.ts +423 -0
  64. package/dist/plugin/index.d.ts.map +1 -0
  65. package/dist/plugin/index.js +253 -0
  66. package/dist/plugin/index.js.map +1 -0
  67. package/dist/plugin/tools/spec-artifact-instr.d.ts +7 -0
  68. package/dist/plugin/tools/spec-artifact-instr.d.ts.map +1 -0
  69. package/dist/plugin/tools/spec-artifact-instr.js +73 -0
  70. package/dist/plugin/tools/spec-artifact-instr.js.map +1 -0
  71. package/dist/plugin/tools/spec-change-archive.d.ts +6 -0
  72. package/dist/plugin/tools/spec-change-archive.d.ts.map +1 -0
  73. package/dist/plugin/tools/spec-change-archive.js +62 -0
  74. package/dist/plugin/tools/spec-change-archive.js.map +1 -0
  75. package/dist/plugin/tools/spec-change-list.d.ts +3 -0
  76. package/dist/plugin/tools/spec-change-list.d.ts.map +1 -0
  77. package/dist/plugin/tools/spec-change-list.js +38 -0
  78. package/dist/plugin/tools/spec-change-list.js.map +1 -0
  79. package/dist/plugin/tools/spec-change-new.d.ts +7 -0
  80. package/dist/plugin/tools/spec-change-new.d.ts.map +1 -0
  81. package/dist/plugin/tools/spec-change-new.js +47 -0
  82. package/dist/plugin/tools/spec-change-new.js.map +1 -0
  83. package/dist/plugin/tools/spec-change-status.d.ts +6 -0
  84. package/dist/plugin/tools/spec-change-status.d.ts.map +1 -0
  85. package/dist/plugin/tools/spec-change-status.js +43 -0
  86. package/dist/plugin/tools/spec-change-status.js.map +1 -0
  87. package/dist/plugin/tools/spec-design-critique.d.ts +20 -0
  88. package/dist/plugin/tools/spec-design-critique.d.ts.map +1 -0
  89. package/dist/plugin/tools/spec-design-critique.js +412 -0
  90. package/dist/plugin/tools/spec-design-critique.js.map +1 -0
  91. package/dist/plugin/tools/spec-design-hifi.d.ts +119 -0
  92. package/dist/plugin/tools/spec-design-hifi.d.ts.map +1 -0
  93. package/dist/plugin/tools/spec-design-hifi.js +653 -0
  94. package/dist/plugin/tools/spec-design-hifi.js.map +1 -0
  95. package/dist/plugin/tools/spec-design-wireframe.d.ts +91 -0
  96. package/dist/plugin/tools/spec-design-wireframe.d.ts.map +1 -0
  97. package/dist/plugin/tools/spec-design-wireframe.js +357 -0
  98. package/dist/plugin/tools/spec-design-wireframe.js.map +1 -0
  99. package/dist/plugin/tools/spec-init.d.ts +9 -0
  100. package/dist/plugin/tools/spec-init.d.ts.map +1 -0
  101. package/dist/plugin/tools/spec-init.js +58 -0
  102. package/dist/plugin/tools/spec-init.js.map +1 -0
  103. package/dist/plugin/tools/spec-integrity-check.d.ts +6 -0
  104. package/dist/plugin/tools/spec-integrity-check.d.ts.map +1 -0
  105. package/dist/plugin/tools/spec-integrity-check.js +19 -0
  106. package/dist/plugin/tools/spec-integrity-check.js.map +1 -0
  107. package/dist/plugin/tools/spec-registry-update.d.ts +3 -0
  108. package/dist/plugin/tools/spec-registry-update.d.ts.map +1 -0
  109. package/dist/plugin/tools/spec-registry-update.js +34 -0
  110. package/dist/plugin/tools/spec-registry-update.js.map +1 -0
  111. package/dist/plugin/tools/spec-schema-list.d.ts +3 -0
  112. package/dist/plugin/tools/spec-schema-list.d.ts.map +1 -0
  113. package/dist/plugin/tools/spec-schema-list.js +28 -0
  114. package/dist/plugin/tools/spec-schema-list.js.map +1 -0
  115. package/dist/plugin/tools/spec-specs-apply.d.ts +7 -0
  116. package/dist/plugin/tools/spec-specs-apply.d.ts.map +1 -0
  117. package/dist/plugin/tools/spec-specs-apply.js +49 -0
  118. package/dist/plugin/tools/spec-specs-apply.js.map +1 -0
  119. package/dist/plugin/tools/spec-task-progress.d.ts +8 -0
  120. package/dist/plugin/tools/spec-task-progress.d.ts.map +1 -0
  121. package/dist/plugin/tools/spec-task-progress.js +96 -0
  122. package/dist/plugin/tools/spec-task-progress.js.map +1 -0
  123. package/dist/plugin/tools/spec-validate.d.ts +21 -0
  124. package/dist/plugin/tools/spec-validate.d.ts.map +1 -0
  125. package/dist/plugin/tools/spec-validate.js +182 -0
  126. package/dist/plugin/tools/spec-validate.js.map +1 -0
  127. package/dist/plugin/tools/spec-verify.d.ts +7 -0
  128. package/dist/plugin/tools/spec-verify.d.ts.map +1 -0
  129. package/dist/plugin/tools/spec-verify.js +50 -0
  130. package/dist/plugin/tools/spec-verify.js.map +1 -0
  131. package/dist/plugin/tools/util.d.ts +25 -0
  132. package/dist/plugin/tools/util.d.ts.map +1 -0
  133. package/dist/plugin/tools/util.js +33 -0
  134. package/dist/plugin/tools/util.js.map +1 -0
  135. package/package.json +61 -0
  136. package/src/agents/orchestrator.md +158 -0
  137. package/src/agents/spec-apply.md +114 -0
  138. package/src/agents/spec-archive.md +103 -0
  139. package/src/agents/spec-propose.md +120 -0
  140. package/src/agents/spec-verify.md +103 -0
  141. package/src/commands/spec-init.md +6 -0
  142. package/src/commands/spec-onboard.md +6 -0
  143. package/src/commands/spec-status.md +6 -0
  144. package/src/commands/spec-sync.md +6 -0
  145. package/src/schemas/hybrid.yaml +144 -0
  146. package/src/schemas/spec-driven.yaml +155 -0
  147. package/src/schemas/tdd.yaml +203 -0
  148. package/src/skills/playwright-cli/SKILL.md +388 -0
  149. package/src/skills/playwright-cli/references/element-attributes.md +23 -0
  150. package/src/skills/playwright-cli/references/playwright-tests.md +39 -0
  151. package/src/skills/playwright-cli/references/request-mocking.md +87 -0
  152. package/src/skills/playwright-cli/references/running-code.md +241 -0
  153. package/src/skills/playwright-cli/references/session-management.md +225 -0
  154. package/src/skills/playwright-cli/references/spec-driven-testing.md +305 -0
  155. package/src/skills/playwright-cli/references/storage-state.md +275 -0
  156. package/src/skills/playwright-cli/references/test-generation.md +134 -0
  157. package/src/skills/playwright-cli/references/tracing.md +139 -0
  158. package/src/skills/playwright-cli/references/video-recording.md +143 -0
  159. package/src/skills/spec-init/SKILL.md +61 -0
  160. package/src/skills/spec-onboard/SKILL.md +178 -0
  161. package/src/skills/spec-status/SKILL.md +72 -0
  162. package/src/skills/spec-sync/SKILL.md +63 -0
  163. package/src/templates/config.yaml +14 -0
  164. package/src/templates/design-hifi.yaml +580 -0
  165. package/src/templates/design-tech.yaml +42 -0
  166. package/src/templates/design-wireframe.yaml +114 -0
  167. package/src/templates/proposal.yaml +43 -0
  168. package/src/templates/registry.yaml +3 -0
  169. package/src/templates/spec.yaml +56 -0
  170. package/src/templates/tasks.yaml +58 -0
package/README.md ADDED
@@ -0,0 +1,229 @@
1
+ # arggon-harness
2
+
3
+ **Spec-driven SDLC workflow for OpenCode**
4
+
5
+ ![license](https://img.shields.io/npm/l/arggon-harness)
6
+
7
+ ## Overview
8
+
9
+ arggon-harness is an OpenCode plugin that enforces a structured, traceable workflow for all code changes. Every modification — feature, bugfix, refactor — flows through five mandatory phases: **explore → propose → apply → verify → archive**.
10
+
11
+ The goal is to replace ad-hoc coding with spec-driven development. Each change produces a trail of YAML artifacts (proposal, specs, design, tasks) that are integrity-verified and traceable from requirement to implementation.
12
+
13
+ The harness ships as an OpenCode plugin with 13 custom tools, 10 workflow skills, 9 slash commands, and a CLI init script that sets up skills, commands, config, and the spec directory in your project.
14
+
15
+ ## Quick Start
16
+
17
+ ```bash
18
+ # Install from npm
19
+ npm install arggon-harness
20
+
21
+ # Or clone and build from source
22
+ git clone https://github.com/Arggon/harness.git
23
+ cd harness
24
+ npm install
25
+
26
+ # Build the plugin
27
+ cd src/plugin && bun run build
28
+
29
+ # Configure your project's .opencode/opencode.json
30
+ # Add the plugin path to your project's .opencode/opencode.json:
31
+ # "plugin": ["/absolute/path/to/harness/src/plugin"]
32
+
33
+ # In your project, run OpenCode — the plugin loads automatically
34
+ # Run the init script to set up skills, commands, and config:
35
+ node /path/to/harness/src/init.ts [target-dir] [--model <model-id>] [--yes]
36
+ ```
37
+
38
+ ## For Contributors
39
+
40
+ ```bash
41
+ git clone https://github.com/Arggon/harness.git
42
+ cd harness
43
+ npm install
44
+
45
+ # Run all tests
46
+ npm test
47
+
48
+ # Run specific test suites
49
+ npm run test:engine # Engine unit tests
50
+ npm run test:tools # Tool tests
51
+ npm run test:hooks # Hook tests
52
+ npm run test:integration # Integration tests
53
+
54
+ # Build plugin after changes
55
+ cd src/plugin && bun run build
56
+ ```
57
+
58
+ The harness is self-hosting — it uses its own spec-driven workflow for development.
59
+
60
+ ## Project Structure
61
+
62
+ After building and configuring in your project:
63
+
64
+ ```
65
+ your-project/
66
+ ├── .opencode/
67
+ │ ├── opencode.json # Plugin, agents, permissions
68
+ │ ├── skills/ # 10 workflow skills
69
+ │ │ ├── spec-explore/
70
+ │ │ ├── spec-propose/
71
+ │ │ ├── spec-apply/
72
+ │ │ ├── spec-verify/
73
+ │ │ ├── spec-archive/
74
+ │ │ ├── spec-sync/
75
+ │ │ ├── spec-status/
76
+ │ │ ├── spec-init/
77
+ │ │ ├── spec-onboard/
78
+ │ │ └── playwright-cli/
79
+ │ └── commands/ # 9 slash commands
80
+ │ ├── spec-explore.md
81
+ │ ├── spec-propose.md
82
+ │ └── ...
83
+ ├── spec/ # Spec data (created by workflow)
84
+ │ ├── config.yaml # Project context and rules
85
+ │ ├── registry.yaml # Change index
86
+ │ ├── main/ # Canonical merged specs
87
+ │ ├── changes/ # Active changes
88
+ │ │ └── <change-name>/
89
+ │ │ ├── meta.yaml
90
+ │ │ ├── proposal.yaml
91
+ │ │ ├── specs/
92
+ │ │ ├── design.yaml
93
+ │ │ ├── tasks.yaml
94
+ │ │ └── .verify/
95
+ │ └── archive/ # Archived changes
96
+ ```
97
+
98
+ ### Source Layout (for contributors)
99
+
100
+ ```
101
+ harness/
102
+ ├── src/
103
+ │ ├── plugin/ # OpenCode plugin
104
+ │ │ ├── index.ts # Entry point
105
+ │ │ ├── engine/ # Core engine (pure TypeScript)
106
+ │ │ ├── tools/ # 13 tool implementations
107
+ │ │ └── hooks/ # System prompt + workflow gate hooks
108
+ │ ├── schemas/ # Workflow schemas (spec-driven, tdd, hybrid)
109
+ │ ├── templates/ # Artifact templates
110
+ │ ├── skills/ # Skill definitions
111
+ │ ├── commands/ # Command wrappers
112
+ │ └── init.ts # CLI init script
113
+ ├── config/
114
+ │ └── default-opencode.json # Config template
115
+ └── test/ # Test suite
116
+ ```
117
+
118
+ ## The Workflow
119
+
120
+ Every code change follows five mandatory phases enforced by the orchestrator agent:
121
+
122
+ | Phase | Command | What Happens |
123
+ |-------|---------|--------------|
124
+ | **Explore** | `/spec-explore` | Investigate the problem, read code, clarify requirements. The orchestrator handles this phase directly. |
125
+ | **Propose** | `/spec-propose` | Create a spec change with proposal, delta specs, design, and task checklist — all as YAML artifacts. |
126
+ | **Apply** | `/spec-apply` | Implement tasks one at a time, respecting dependencies. Each task is tracked and marked complete. |
127
+ | **Verify** | `/spec-verify` | 5-dimension verification: integrity, compliance, tasks, coherence, regression. |
128
+ | **Archive** | `/spec-archive` | Sync delta specs to main specs, move change to `spec/archive/`, preserve audit trail. |
129
+
130
+ No shortcuts. No "quick fixes" that skip the workflow. The orchestrator agent enforces this for every change, even when the user says "just fix it."
131
+
132
+ ## What You Get
133
+
134
+ ### Plugin — 13 Tools
135
+
136
+ | Tool | Purpose |
137
+ |------|---------|
138
+ | `spec_change_new` | Create a new spec change |
139
+ | `spec_change_status` | Show artifact and task status |
140
+ | `spec_change_list` | List all active changes |
141
+ | `spec_change_archive` | Archive a completed change |
142
+ | `spec_artifact_instr` | Get artifact instructions and templates |
143
+ | `spec_validate` | Validate artifact structure and integrity |
144
+ | `spec_verify` | Run 5-dimension verification |
145
+ | `spec_specs_apply` | Apply delta specs to main specs |
146
+ | `spec_task_progress` | Track and update task completion |
147
+ | `spec_integrity_check` | SHA-256 integrity verification |
148
+ | `spec_schema_list` | List available workflow schemas |
149
+ | `spec_registry_update` | Update the change registry |
150
+ | `spec_init` | Initialize spec directory structure |
151
+
152
+ ### Skills — 10
153
+
154
+ | Skill | Description |
155
+ |-------|-------------|
156
+ | `spec-explore` | Thinking partner for investigating problems and clarifying requirements |
157
+ | `spec-propose` | Generate a complete change proposal with all artifacts in one step |
158
+ | `spec-apply` | Implement tasks from a spec change |
159
+ | `spec-verify` | 5-dimension verification of implementation |
160
+ | `spec-archive` | Archive completed changes and sync delta specs |
161
+ | `spec-sync` | Sync delta specs to main without archiving |
162
+ | `spec-status` | Show artifact completion, task progress, and verification state |
163
+ | `spec-init` | Initialize the `spec/` directory structure |
164
+ | `spec-onboard` | Guided onboarding tutorial through a complete workflow cycle |
165
+ | `playwright-cli` | Browser automation for testing |
166
+
167
+ ### Commands — 9
168
+
169
+ Slash commands that map to skills: `/spec-explore`, `/spec-propose`, `/spec-apply`, `/spec-verify`, `/spec-archive`, `/spec-sync`, `/spec-status`, `/spec-init`, `/spec-onboard`.
170
+
171
+ ### Schemas — 3
172
+
173
+ | Schema | Use Case |
174
+ |--------|----------|
175
+ | `spec-driven` | Default. Specs first, then implementation. |
176
+ | `tdd` | Test-driven. Write tests before implementation. |
177
+ | `hybrid` | Combine spec-driven and TDD approaches. |
178
+
179
+ ## Configuration
180
+
181
+ The plugin registers in `.opencode/opencode.json`:
182
+
183
+ ```json
184
+ {
185
+ "plugin": ["/absolute/path/to/harness/src/plugin"],
186
+ "default_agent": "orchestrator",
187
+ "agent": {
188
+ "orchestrator": { ... },
189
+ "spec-propose": { ... },
190
+ "spec-apply": { ... },
191
+ "spec-verify": { ... },
192
+ "spec-archive": { ... }
193
+ },
194
+ "permission": {
195
+ "spec_change_new": "allow",
196
+ "spec_change_status": "allow",
197
+ "spec_change_list": "allow",
198
+ "spec_artifact_instr": "allow",
199
+ "spec_validate": "allow",
200
+ "spec_specs_apply": "allow",
201
+ "spec_change_archive": "ask",
202
+ "spec_schema_list": "allow",
203
+ "spec_verify": "allow",
204
+ "spec_task_progress": "allow",
205
+ "spec_integrity_check": "allow",
206
+ "spec_registry_update": "allow"
207
+ }
208
+ }
209
+ ```
210
+
211
+ If `opencode.json` already exists, entries are merged without overwriting existing config.
212
+
213
+ ### Customization
214
+
215
+ Edit `.opencode/opencode.json` after setup to:
216
+ - Change the model for any agent
217
+ - Adjust permissions
218
+ - Add custom agents
219
+ - Modify agent prompts
220
+
221
+ ## Requirements
222
+
223
+ - Node.js >= 18
224
+ - [OpenCode](https://opencode.ai) installed
225
+ - TypeScript ESM project (for plugin development)
226
+
227
+ ## License
228
+
229
+ MIT
@@ -0,0 +1,21 @@
1
+ {
2
+ "$schema": "https://opencode.ai/config.json",
3
+ "plugin": [
4
+ "arggon-harness"
5
+ ],
6
+ "default_agent": "orchestrator",
7
+ "permission": {
8
+ "spec_change_new": "allow",
9
+ "spec_change_status": "allow",
10
+ "spec_change_list": "allow",
11
+ "spec_artifact_instr": "allow",
12
+ "spec_validate": "allow",
13
+ "spec_specs_apply": "allow",
14
+ "spec_change_archive": "ask",
15
+ "spec_schema_list": "allow",
16
+ "spec_verify": "allow",
17
+ "spec_task_progress": "allow",
18
+ "spec_integrity_check": "allow",
19
+ "spec_registry_update": "allow"
20
+ }
21
+ }
package/dist/init.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=init.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../src/init.ts"],"names":[],"mappings":""}
package/dist/init.js ADDED
@@ -0,0 +1,406 @@
1
+ #!/usr/bin/env node
2
+ import * as fsp from "fs/promises";
3
+ import * as path from "path";
4
+ import * as readline from "readline";
5
+ import { fileURLToPath } from "url";
6
+ function parseArgs() {
7
+ const argv = process.argv.slice(2);
8
+ let targetDir = process.cwd();
9
+ let model = "anthropic/claude-sonnet-4-20250514";
10
+ let yes = false;
11
+ for (let i = 0; i < argv.length; i++) {
12
+ const arg = argv[i];
13
+ if (arg === "--model" && i + 1 < argv.length) {
14
+ model = argv[++i];
15
+ }
16
+ else if (arg === "--yes" || arg === "-y") {
17
+ yes = true;
18
+ }
19
+ else if (!arg.startsWith("-")) {
20
+ targetDir = path.resolve(arg);
21
+ }
22
+ else {
23
+ console.error(`Unknown option: ${arg}`);
24
+ console.error("Usage: harness-init [target-dir] [--model <id>] [--yes|-y]");
25
+ process.exit(1);
26
+ }
27
+ }
28
+ return { targetDir, model, yes };
29
+ }
30
+ // ─── Asset Resolution ─────────────────────────────────────────────────────────
31
+ const scriptDir = path.dirname(fileURLToPath(import.meta.url));
32
+ const packageRoot = path.join(scriptDir, ".."); // dist/init.js → package root
33
+ const skillsSource = path.join(packageRoot, "src", "skills");
34
+ const commandsSource = path.join(packageRoot, "src", "commands");
35
+ const agentsSource = path.join(packageRoot, "src", "agents");
36
+ const configTemplate = path.join(packageRoot, "config", "default-opencode.json");
37
+ // ─── Prompting ────────────────────────────────────────────────────────────────
38
+ async function promptYesNo(question, autoYes) {
39
+ if (autoYes)
40
+ return true;
41
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
42
+ return new Promise((resolve) => {
43
+ rl.question(`${question} (y/n) `, (answer) => {
44
+ rl.close();
45
+ const trimmed = answer.trim().toLowerCase();
46
+ resolve(trimmed === "y" || trimmed === "yes");
47
+ });
48
+ });
49
+ }
50
+ // ─── Recursive Directory Copy ─────────────────────────────────────────────────
51
+ async function copyDirRecursive(src, dest) {
52
+ await fsp.mkdir(dest, { recursive: true });
53
+ const entries = await fsp.readdir(src, { withFileTypes: true });
54
+ for (const entry of entries) {
55
+ const srcPath = path.join(src, entry.name);
56
+ const destPath = path.join(dest, entry.name);
57
+ if (entry.isDirectory()) {
58
+ await copyDirRecursive(srcPath, destPath);
59
+ }
60
+ else {
61
+ await fsp.copyFile(srcPath, destPath);
62
+ }
63
+ }
64
+ }
65
+ // ─── Existence Check Helper ───────────────────────────────────────────────────
66
+ async function exists(filePath) {
67
+ try {
68
+ await fsp.access(filePath);
69
+ return true;
70
+ }
71
+ catch {
72
+ return false;
73
+ }
74
+ }
75
+ // ─── Prune Helper ──────────────────────────────────────────────────────────────
76
+ async function pruneTargetDir(srcDir, targetDir, summaryArray) {
77
+ // Skip if target doesn't exist — nothing to prune
78
+ if (!(await exists(targetDir)))
79
+ return;
80
+ // Read source entries — collect names of directories and .md files
81
+ const srcEntries = await fsp.readdir(srcDir, { withFileTypes: true });
82
+ const srcNames = new Set();
83
+ for (const entry of srcEntries) {
84
+ if (entry.isDirectory() || entry.name.endsWith(".md")) {
85
+ srcNames.add(entry.name);
86
+ }
87
+ }
88
+ // Read target entries — delete anything not present in source
89
+ const targetEntries = await fsp.readdir(targetDir, { withFileTypes: true });
90
+ for (const entry of targetEntries) {
91
+ if (entry.isDirectory() || entry.name.endsWith(".md")) {
92
+ if (!srcNames.has(entry.name)) {
93
+ const targetPath = path.join(targetDir, entry.name);
94
+ if (entry.isDirectory()) {
95
+ await fsp.rm(targetPath, { recursive: true, force: true });
96
+ }
97
+ else {
98
+ await fsp.unlink(targetPath);
99
+ }
100
+ summaryArray.push(entry.name);
101
+ }
102
+ }
103
+ }
104
+ }
105
+ // ─── Copy Skills ──────────────────────────────────────────────────────────────
106
+ async function copySkills(targetDir, autoYes, summary) {
107
+ const skillsDest = path.join(targetDir, ".opencode", "skills");
108
+ await fsp.mkdir(skillsDest, { recursive: true });
109
+ const skillEntries = await fsp.readdir(skillsSource, { withFileTypes: true });
110
+ const skillDirs = skillEntries.filter((e) => e.isDirectory());
111
+ for (const entry of skillDirs) {
112
+ const destSkillDir = path.join(skillsDest, entry.name);
113
+ const srcSkillDir = path.join(skillsSource, entry.name);
114
+ if (await exists(destSkillDir)) {
115
+ const overwrite = await promptYesNo(` Skill "${entry.name}" already exists. Overwrite?`, autoYes);
116
+ if (overwrite) {
117
+ await fsp.rm(destSkillDir, { recursive: true, force: true });
118
+ await copyDirRecursive(srcSkillDir, destSkillDir);
119
+ summary.skillsCopied.push(entry.name);
120
+ }
121
+ else {
122
+ summary.skillsSkipped.push(entry.name);
123
+ }
124
+ }
125
+ else {
126
+ await copyDirRecursive(srcSkillDir, destSkillDir);
127
+ summary.skillsCopied.push(entry.name);
128
+ }
129
+ }
130
+ await pruneTargetDir(skillsSource, skillsDest, summary.skillsPruned);
131
+ }
132
+ // ─── Copy Commands ────────────────────────────────────────────────────────────
133
+ async function copyCommands(targetDir, autoYes, summary) {
134
+ const commandsDest = path.join(targetDir, ".opencode", "commands");
135
+ await fsp.mkdir(commandsDest, { recursive: true });
136
+ const files = await fsp.readdir(commandsSource);
137
+ const mdFiles = files.filter((f) => f.endsWith(".md"));
138
+ for (const file of mdFiles) {
139
+ const destFile = path.join(commandsDest, file);
140
+ const srcFile = path.join(commandsSource, file);
141
+ if (await exists(destFile)) {
142
+ const overwrite = await promptYesNo(` Command "${file}" already exists. Overwrite?`, autoYes);
143
+ if (overwrite) {
144
+ await fsp.copyFile(srcFile, destFile);
145
+ summary.commandsCopied.push(file);
146
+ }
147
+ else {
148
+ summary.commandsSkipped.push(file);
149
+ }
150
+ }
151
+ else {
152
+ await fsp.copyFile(srcFile, destFile);
153
+ summary.commandsCopied.push(file);
154
+ }
155
+ }
156
+ await pruneTargetDir(commandsSource, commandsDest, summary.commandsPruned);
157
+ }
158
+ // ─── Copy Agents ──────────────────────────────────────────────────────────────
159
+ async function copyAgents(targetDir, autoYes, summary) {
160
+ const agentsDest = path.join(targetDir, ".opencode", "agents");
161
+ await fsp.mkdir(agentsDest, { recursive: true });
162
+ const files = await fsp.readdir(agentsSource);
163
+ const mdFiles = files.filter((f) => f.endsWith(".md"));
164
+ for (const file of mdFiles) {
165
+ const destFile = path.join(agentsDest, file);
166
+ const srcFile = path.join(agentsSource, file);
167
+ if (await exists(destFile)) {
168
+ const overwrite = await promptYesNo(` Agent "${file}" already exists. Overwrite?`, autoYes);
169
+ if (overwrite) {
170
+ await fsp.copyFile(srcFile, destFile);
171
+ summary.agentsCopied.push(file);
172
+ }
173
+ else {
174
+ summary.agentsSkipped.push(file);
175
+ }
176
+ }
177
+ else {
178
+ await fsp.copyFile(srcFile, destFile);
179
+ summary.agentsCopied.push(file);
180
+ }
181
+ }
182
+ await pruneTargetDir(agentsSource, agentsDest, summary.agentsPruned);
183
+ }
184
+ // ─── Generate / Merge opencode.json ───────────────────────────────────────────
185
+ async function generateConfig(targetDir, model, summary) {
186
+ const opencodeJsonPath = path.join(targetDir, ".opencode", "opencode.json");
187
+ // Read and substitute template
188
+ let templateContent = await fsp.readFile(configTemplate, "utf-8");
189
+ templateContent = templateContent.replace(/\{\{MODEL_ID\}\}/g, model);
190
+ const templateConfig = JSON.parse(templateContent);
191
+ if (await exists(opencodeJsonPath)) {
192
+ // Merge into existing config — overhauled for replace semantics
193
+ const existingRaw = await fsp.readFile(opencodeJsonPath, "utf-8");
194
+ const existingConfig = JSON.parse(existingRaw);
195
+ const changes = [];
196
+ // 1. Add arggon-harness to plugin array if not already present
197
+ // Preserves any existing user plugins.
198
+ if (!existingConfig.plugin.includes("arggon-harness")) {
199
+ existingConfig.plugin.push("arggon-harness");
200
+ changes.push("added arggon-harness plugin");
201
+ }
202
+ // 2. Add or update permission block from template
203
+ existingConfig.permission = {
204
+ ...existingConfig.permission,
205
+ ...templateConfig.permission,
206
+ };
207
+ changes.push("updated permission block");
208
+ // 3. Preserve every other user setting — $schema, default_agent, references,
209
+ // model, agent, custom keys — left untouched.
210
+ // 4. Set configUpdated description with what changed
211
+ if (changes.length > 0) {
212
+ summary.configUpdated = changes.join(", ");
213
+ }
214
+ await fsp.writeFile(opencodeJsonPath, JSON.stringify(existingConfig, null, 2) + "\n");
215
+ summary.configMerged = true;
216
+ }
217
+ else {
218
+ // Fresh write (unchanged from current behavior)
219
+ await fsp.mkdir(path.join(targetDir, ".opencode"), { recursive: true });
220
+ await fsp.writeFile(opencodeJsonPath, templateContent);
221
+ summary.configCreated = true;
222
+ }
223
+ }
224
+ // ─── Initialize spec/ Directory ───────────────────────────────────────────────
225
+ async function initSpecDirectory(targetDir, autoYes, summary) {
226
+ const specConfigPath = path.join(targetDir, "spec", "config.yaml");
227
+ if (await exists(specConfigPath)) {
228
+ console.log(" spec/ directory already exists — skipping initialization");
229
+ summary.specSkipped = true;
230
+ return;
231
+ }
232
+ const initSpec = await promptYesNo("\nInitialize spec/ directory for spec-driven workflow?", autoYes);
233
+ if (!initSpec) {
234
+ summary.specDeclined = true;
235
+ return;
236
+ }
237
+ // Create directory structure
238
+ const specDirs = ["spec", "spec/changes", "spec/main", "spec/archive"];
239
+ for (const d of specDirs) {
240
+ await fsp.mkdir(path.join(targetDir, d), { recursive: true });
241
+ }
242
+ // Create config.yaml
243
+ const configYaml = `schema: spec-driven
244
+
245
+ context: |
246
+ <describe-your-project-here>
247
+
248
+ rules:
249
+ proposal:
250
+ - Keep proposals focused on a single change
251
+ specs:
252
+ - Use RFC 2119 language (SHALL/MUST/SHOULD/MAY)
253
+ design:
254
+ - Include at least one alternative considered
255
+ tasks:
256
+ - Each task should be independently testable
257
+ `;
258
+ await fsp.writeFile(path.join(targetDir, "spec", "config.yaml"), configYaml);
259
+ // Create registry.yaml
260
+ const registryYaml = `changes: []
261
+
262
+ last_updated: ${new Date().toISOString()}
263
+ `;
264
+ await fsp.writeFile(path.join(targetDir, "spec", "registry.yaml"), registryYaml);
265
+ summary.specInitialized = true;
266
+ }
267
+ // ─── Print Summary ────────────────────────────────────────────────────────────
268
+ function printSummary(summary) {
269
+ console.log("\n✅ arggon-harness initialized!\n");
270
+ console.log("Created:");
271
+ console.log(` .opencode/skills/ (${summary.skillsCopied.length} skills)`);
272
+ console.log(` .opencode/commands/ (${summary.commandsCopied.length} commands)`);
273
+ console.log(` .opencode/agents/ (${summary.agentsCopied.length} agents)`);
274
+ if (summary.configCreated) {
275
+ console.log(" .opencode/opencode.json");
276
+ }
277
+ else if (summary.configMerged) {
278
+ console.log(" .opencode/opencode.json (merged)");
279
+ }
280
+ // Pruned items
281
+ const allPruned = [
282
+ ...summary.skillsPruned.map((s) => `.opencode/skills/${s}/`),
283
+ ...summary.commandsPruned.map((c) => `.opencode/commands/${c}`),
284
+ ...summary.agentsPruned.map((a) => `.opencode/agents/${a}`),
285
+ ];
286
+ if (allPruned.length > 0) {
287
+ console.log("\nPruned:");
288
+ for (const p of allPruned) {
289
+ console.log(` ${p}`);
290
+ }
291
+ }
292
+ // Config updates
293
+ if (summary.configUpdated) {
294
+ console.log(`\nConfig updated: ${summary.configUpdated}`);
295
+ }
296
+ // Skipped files
297
+ const allSkipped = [
298
+ ...summary.skillsSkipped.map((s) => `.opencode/skills/${s}/`),
299
+ ...summary.commandsSkipped.map((c) => `.opencode/commands/${c}`),
300
+ ...summary.agentsSkipped.map((a) => `.opencode/agents/${a}`),
301
+ ];
302
+ if (allSkipped.length > 0) {
303
+ console.log("\nSkipped (already existed):");
304
+ for (const s of allSkipped) {
305
+ console.log(` ${s}`);
306
+ }
307
+ }
308
+ // Spec directory status
309
+ if (summary.specInitialized) {
310
+ console.log("\nSpec directory:");
311
+ console.log(" spec/ (initialized)");
312
+ }
313
+ else if (summary.specSkipped) {
314
+ console.log("\nSpec directory:");
315
+ console.log(" spec/ (already existed — skipped)");
316
+ }
317
+ // Next steps
318
+ console.log("\nNext steps:");
319
+ console.log(" 1. Review .opencode/opencode.json");
320
+ if (summary.specInitialized) {
321
+ console.log(" 2. Edit spec/config.yaml to describe your project");
322
+ }
323
+ else if (summary.specDeclined) {
324
+ console.log(" 2. Run /spec-init to initialize the spec directory");
325
+ }
326
+ else if (summary.specSkipped) {
327
+ console.log(" 2. Review spec/config.yaml for your project context");
328
+ }
329
+ else {
330
+ console.log(" 2. Run /spec-init to configure your project context");
331
+ }
332
+ console.log(" 3. Start OpenCode and create your first change");
333
+ console.log();
334
+ }
335
+ // ─── Main ─────────────────────────────────────────────────────────────────────
336
+ async function main() {
337
+ const args = parseArgs();
338
+ const summary = {
339
+ skillsCopied: [],
340
+ skillsSkipped: [],
341
+ commandsCopied: [],
342
+ commandsSkipped: [],
343
+ agentsCopied: [],
344
+ agentsSkipped: [],
345
+ configCreated: false,
346
+ configMerged: false,
347
+ specInitialized: false,
348
+ specSkipped: false,
349
+ specDeclined: false,
350
+ skillsPruned: [],
351
+ commandsPruned: [],
352
+ agentsPruned: [],
353
+ configUpdated: null,
354
+ };
355
+ console.log(`\n🔧 arggon-harness Init`);
356
+ console.log(` Target: ${args.targetDir}`);
357
+ console.log(` Model: ${args.model}\n`);
358
+ // Validate asset sources exist
359
+ if (!(await exists(skillsSource))) {
360
+ console.error(`Error: Skills source not found at ${skillsSource}`);
361
+ console.error("Is the package installed correctly?");
362
+ process.exit(1);
363
+ }
364
+ if (!(await exists(commandsSource))) {
365
+ console.error(`Error: Commands source not found at ${commandsSource}`);
366
+ console.error("Is the package installed correctly?");
367
+ process.exit(1);
368
+ }
369
+ if (!(await exists(agentsSource))) {
370
+ console.error(`Error: Agents source not found at ${agentsSource}`);
371
+ console.error("Is the package installed correctly?");
372
+ process.exit(1);
373
+ }
374
+ if (!(await exists(configTemplate))) {
375
+ console.error(`Error: Config template not found at ${configTemplate}`);
376
+ console.error("Is the package installed correctly?");
377
+ process.exit(1);
378
+ }
379
+ // Ensure target directory exists
380
+ await fsp.mkdir(args.targetDir, { recursive: true });
381
+ // Ensure .opencode/ directory exists
382
+ await fsp.mkdir(path.join(args.targetDir, ".opencode"), { recursive: true });
383
+ // Make sure .opencode/agents/ exists
384
+ await fsp.mkdir(path.join(args.targetDir, ".opencode", "agents"), { recursive: true });
385
+ // Step 1a: Copy skills
386
+ console.log("Copying skills...");
387
+ await copySkills(args.targetDir, args.yes, summary);
388
+ // Step 1b: Copy agents
389
+ console.log("Copying agents...");
390
+ await copyAgents(args.targetDir, args.yes, summary);
391
+ // Step 2: Copy commands
392
+ console.log("Copying commands...");
393
+ await copyCommands(args.targetDir, args.yes, summary);
394
+ // Step 3: Generate/merge opencode.json
395
+ console.log("Configuring opencode.json...");
396
+ await generateConfig(args.targetDir, args.model, summary);
397
+ // Step 4: Offer spec/ initialization
398
+ await initSpecDirectory(args.targetDir, args.yes, summary);
399
+ // Step 5: Print summary
400
+ printSummary(summary);
401
+ }
402
+ main().catch((err) => {
403
+ console.error(`\n❌ Error: ${err.message}`);
404
+ process.exit(1);
405
+ });
406
+ //# sourceMappingURL=init.js.map