niuma-harness 0.0.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 xudeyiyi
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,105 @@
1
+ # Niuma Harness
2
+
3
+ Initialize an AI engineering harness for a project workspace.
4
+
5
+ Niuma Harness creates a lightweight documentation scaffold that helps AI coding tools understand project context, rules, workflows, and task notes.
6
+
7
+ ## Quick start
8
+
9
+ ```bash
10
+ npx niuma-harness init . --agent claude
11
+ ```
12
+
13
+ Interactive mode is available when `--agent` is omitted in a TTY:
14
+
15
+ ```bash
16
+ npx niuma-harness init
17
+ ```
18
+
19
+ ## CLI
20
+
21
+ ```bash
22
+ niuma-harness init [target] [options]
23
+ ```
24
+
25
+ ### Options
26
+
27
+ | Option | Description |
28
+ |---|---|
29
+ | `--agent <name>` | `claude`, `codex`, `opencode`, or `multi` |
30
+ | `--harness-dir <name>` | Directory to create inside target, default: `harness` |
31
+ | `--rules <mode>` | `copy` or `empty`, default: `copy` |
32
+ | `--flat` | Write directly into target instead of `target/harness` |
33
+ | `--force` | Overwrite existing scaffold files |
34
+ | `--dry-run` | Print planned actions without writing files |
35
+ | `-h`, `--help` | Show help |
36
+
37
+ ## Examples
38
+
39
+ ```bash
40
+ npx niuma-harness init . --agent claude
41
+ npx niuma-harness init ./workspace --agent codex --rules empty
42
+ npx niuma-harness init ./workspace --agent opencode --dry-run
43
+ npx niuma-harness init ./workspace --agent multi --harness-dir ai-harness
44
+ npx niuma-harness init ./workspace --agent claude --flat
45
+ ```
46
+
47
+ ## Agent modes
48
+
49
+ | Agent | Generated entry file |
50
+ |---|---|
51
+ | `claude` | `CLAUDE.md` |
52
+ | `codex` | `AGENTS.md` |
53
+ | `opencode` | `AGENTS.md` |
54
+ | `multi` | `CLAUDE.md` and `AGENTS.md` |
55
+
56
+ ## Generated structure
57
+
58
+ Default output:
59
+
60
+ ```text
61
+ workspace/
62
+ harness/
63
+ CLAUDE.md or AGENTS.md
64
+ HARNESS_GUIDE.md
65
+ docs/
66
+ index.md
67
+ project-context.md
68
+ automation/
69
+ hooks.md
70
+ process/
71
+ task-triage.md
72
+ bugfix.md
73
+ feature-development.md
74
+ rules/
75
+ common/
76
+ coding-style.md
77
+ testing.md
78
+ security.md
79
+ tasks/
80
+ README.md
81
+ ```
82
+
83
+ Use `--flat` to write the same scaffold directly into the target directory instead of creating `harness/`.
84
+
85
+ ## Rules mode
86
+
87
+ `--rules copy` is the default and creates starter rule content.
88
+
89
+ ```bash
90
+ npx niuma-harness init . --agent claude --rules copy
91
+ ```
92
+
93
+ `--rules empty` creates the same rule files with empty content, useful when a team wants to fill its own rules from scratch.
94
+
95
+ ```bash
96
+ npx niuma-harness init . --agent claude --rules empty
97
+ ```
98
+
99
+ ## Development
100
+
101
+ ```bash
102
+ npm test
103
+ npm run pack:dry
104
+ node bin/niuma-harness.js init . --agent claude --dry-run
105
+ ```
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { main } = require('../src/cli');
4
+
5
+ main(process.argv.slice(2)).catch((error) => {
6
+ console.error(`Error: ${error.message}`);
7
+ process.exitCode = 1;
8
+ });
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "niuma-harness",
3
+ "version": "0.0.1",
4
+ "description": "Initialize an AI engineering harness for a project workspace.",
5
+ "type": "commonjs",
6
+ "bin": {
7
+ "niuma-harness": "bin/niuma-harness.js"
8
+ },
9
+ "files": [
10
+ "bin",
11
+ "src",
12
+ "templates",
13
+ "README.md",
14
+ "LICENSE"
15
+ ],
16
+ "scripts": {
17
+ "test": "node test/cli.test.js",
18
+ "check": "npm test",
19
+ "pack:dry": "npm pack --dry-run"
20
+ },
21
+ "keywords": [
22
+ "ai",
23
+ "harness",
24
+ "claude-code",
25
+ "codex",
26
+ "openCode",
27
+ "agent"
28
+ ],
29
+ "repository": {
30
+ "type": "git",
31
+ "url": "git+https://github.com/739188210/niuma-harness.git"
32
+ },
33
+ "bugs": {
34
+ "url": "https://github.com/739188210/niuma-harness/issues"
35
+ },
36
+ "homepage": "https://github.com/739188210/niuma-harness#readme",
37
+ "engines": {
38
+ "node": ">=18"
39
+ },
40
+ "license": "MIT",
41
+ "author": "xudeyiyi"
42
+ }
package/src/args.js ADDED
@@ -0,0 +1,176 @@
1
+ const SUPPORTED_AGENTS = new Set(['claude', 'codex', 'opencode', 'multi']);
2
+ const SUPPORTED_RULES = new Set(['copy', 'empty']);
3
+
4
+ function parseArgs(argv) {
5
+ const options = {
6
+ command: null,
7
+ targetDir: null,
8
+ agent: null,
9
+ rules: 'copy',
10
+ harnessDir: 'harness',
11
+ flat: false,
12
+ force: false,
13
+ dryRun: false,
14
+ help: false,
15
+ };
16
+
17
+ for (let index = 0; index < argv.length; index += 1) {
18
+ const arg = argv[index];
19
+
20
+ if (arg === '-h' || arg === '--help') {
21
+ options.help = true;
22
+ continue;
23
+ }
24
+
25
+ if (arg === '--flat') {
26
+ options.flat = true;
27
+ continue;
28
+ }
29
+
30
+ if (arg === '--force') {
31
+ options.force = true;
32
+ continue;
33
+ }
34
+
35
+ if (arg === '--dry-run') {
36
+ options.dryRun = true;
37
+ continue;
38
+ }
39
+
40
+ if (arg === '--agent' || arg === '--rules' || arg === '--harness-dir') {
41
+ const value = argv[index + 1];
42
+ if (!value || value.startsWith('-')) {
43
+ throw new Error(`${arg} requires a value.`);
44
+ }
45
+ assignOption(options, arg.slice(2), value);
46
+ index += 1;
47
+ continue;
48
+ }
49
+
50
+ if (arg.startsWith('--agent=') || arg.startsWith('--rules=') || arg.startsWith('--harness-dir=')) {
51
+ const [name, value] = splitLongOption(arg);
52
+ assignOption(options, name, value);
53
+ continue;
54
+ }
55
+
56
+ if (arg.startsWith('-')) {
57
+ throw new Error(`Unknown option: ${arg}`);
58
+ }
59
+
60
+ if (!options.command) {
61
+ options.command = arg;
62
+ continue;
63
+ }
64
+
65
+ if (!options.targetDir) {
66
+ options.targetDir = arg;
67
+ continue;
68
+ }
69
+
70
+ throw new Error(`Unexpected argument: ${arg}`);
71
+ }
72
+
73
+ options.agent = normalizeAgent(options.agent);
74
+ options.rules = normalizeRules(options.rules);
75
+ options.harnessDir = normalizeHarnessDir(options.harnessDir);
76
+
77
+ return options;
78
+ }
79
+
80
+ function assignOption(options, name, value) {
81
+ if (name === 'agent') {
82
+ options.agent = value;
83
+ return;
84
+ }
85
+
86
+ if (name === 'rules') {
87
+ options.rules = value;
88
+ return;
89
+ }
90
+
91
+ if (name === 'harness-dir') {
92
+ options.harnessDir = value;
93
+ return;
94
+ }
95
+
96
+ throw new Error(`Unknown option: --${name}`);
97
+ }
98
+
99
+ function splitLongOption(arg) {
100
+ const equalsIndex = arg.indexOf('=');
101
+ const name = arg.slice(2, equalsIndex);
102
+ const value = arg.slice(equalsIndex + 1);
103
+
104
+ if (!value) {
105
+ throw new Error(`--${name} requires a value.`);
106
+ }
107
+
108
+ return [name, value];
109
+ }
110
+
111
+ function normalizeAgent(agent) {
112
+ if (!agent) {
113
+ return null;
114
+ }
115
+
116
+ const value = String(agent).trim().toLowerCase();
117
+ if (SUPPORTED_AGENTS.has(value)) {
118
+ return value;
119
+ }
120
+
121
+ throw new Error(`--agent must be one of: claude, codex, opencode, multi. Received: ${agent}`);
122
+ }
123
+
124
+ function normalizeRules(rules) {
125
+ const value = String(rules || 'copy').trim().toLowerCase();
126
+ if (SUPPORTED_RULES.has(value)) {
127
+ return value;
128
+ }
129
+
130
+ throw new Error(`--rules must be one of: copy, empty. Received: ${rules}`);
131
+ }
132
+
133
+ function normalizeHarnessDir(harnessDir) {
134
+ const value = String(harnessDir || 'harness').trim();
135
+ if (!value) {
136
+ throw new Error('--harness-dir cannot be empty.');
137
+ }
138
+
139
+ if (value === '.' || value.includes('..') || value.includes('/') || value.includes('\\')) {
140
+ throw new Error('--harness-dir must be a simple directory name.');
141
+ }
142
+
143
+ if (!/^[A-Za-z0-9._-]+$/.test(value)) {
144
+ throw new Error('--harness-dir may only contain letters, numbers, dots, underscores, and dashes.');
145
+ }
146
+
147
+ return value;
148
+ }
149
+
150
+ function getHelpText() {
151
+ return `Usage:
152
+ niuma-harness init [target] [options]
153
+
154
+ Options:
155
+ --agent <name> claude | codex | opencode | multi
156
+ --harness-dir <name> Directory to create inside target, default: harness
157
+ --rules <mode> copy | empty, default: copy
158
+ --flat Write directly into target instead of target/harness
159
+ --force Overwrite existing scaffold files
160
+ --dry-run Print planned actions without writing files
161
+ -h, --help Show help
162
+
163
+ Examples:
164
+ niuma-harness init . --agent claude
165
+ niuma-harness init D:\\work\\app --agent codex --rules empty
166
+ niuma-harness init . --agent multi --harness-dir ai-harness
167
+ niuma-harness init . --agent opencode --flat --dry-run`;
168
+ }
169
+
170
+ module.exports = {
171
+ parseArgs,
172
+ normalizeAgent,
173
+ normalizeRules,
174
+ normalizeHarnessDir,
175
+ getHelpText,
176
+ };
package/src/cli.js ADDED
@@ -0,0 +1,23 @@
1
+ const { parseArgs, getHelpText } = require('./args');
2
+ const { chooseAgent } = require('./prompts');
3
+ const { runInit } = require('./scaffold');
4
+
5
+ async function main(argv) {
6
+ const options = parseArgs(argv);
7
+
8
+ if (options.help || !options.command) {
9
+ console.log(getHelpText());
10
+ return;
11
+ }
12
+
13
+ if (options.command !== 'init') {
14
+ throw new Error(`Unknown command: ${options.command}. Only "init" is supported.`);
15
+ }
16
+
17
+ options.agent = await chooseAgent(options.agent);
18
+ runInit(options);
19
+ }
20
+
21
+ module.exports = {
22
+ main,
23
+ };
package/src/prompts.js ADDED
@@ -0,0 +1,61 @@
1
+ const readline = require('readline');
2
+ const { normalizeAgent } = require('./args');
3
+
4
+ function ask(question) {
5
+ const rl = readline.createInterface({
6
+ input: process.stdin,
7
+ output: process.stdout,
8
+ });
9
+
10
+ return new Promise((resolve) => {
11
+ rl.question(question, (answer) => {
12
+ rl.close();
13
+ resolve(answer);
14
+ });
15
+ });
16
+ }
17
+
18
+ async function chooseAgent(agent) {
19
+ const normalized = normalizeAgent(agent);
20
+ if (normalized) {
21
+ return normalized;
22
+ }
23
+
24
+ if (!process.stdin.isTTY) {
25
+ throw new Error('Missing --agent. Use --agent claude, --agent codex, --agent opencode, or --agent multi.');
26
+ }
27
+
28
+ while (true) {
29
+ const answer = await ask([
30
+ 'Choose AI coding agent:',
31
+ ' 1. claude -> generate CLAUDE.md',
32
+ ' 2. codex -> generate AGENTS.md',
33
+ ' 3. opencode -> generate AGENTS.md',
34
+ ' 4. multi -> generate CLAUDE.md and AGENTS.md',
35
+ 'Enter claude/codex/opencode/multi or 1/2/3/4: ',
36
+ ].join('\n'));
37
+
38
+ try {
39
+ return normalizeAgentAlias(answer);
40
+ } catch {
41
+ console.log('Please enter claude, codex, opencode, multi, 1, 2, 3, or 4.');
42
+ }
43
+ }
44
+ }
45
+
46
+ function normalizeAgentAlias(value) {
47
+ const normalized = String(value || '').trim().toLowerCase();
48
+ const aliases = {
49
+ 1: 'claude',
50
+ 2: 'codex',
51
+ 3: 'opencode',
52
+ 4: 'multi',
53
+ };
54
+
55
+ return normalizeAgent(aliases[normalized] || normalized);
56
+ }
57
+
58
+ module.exports = {
59
+ ask,
60
+ chooseAgent,
61
+ };
@@ -0,0 +1,207 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ const ROOT_DIR = path.resolve(__dirname, '..');
5
+ const TEMPLATE_DIR = path.join(ROOT_DIR, 'templates');
6
+ const MANIFEST_PATH = path.join(TEMPLATE_DIR, 'manifest.json');
7
+
8
+ function runInit(options) {
9
+ const workspaceDir = path.resolve(options.targetDir || '.');
10
+ const targetDir = options.flat ? workspaceDir : path.join(workspaceDir, options.harnessDir);
11
+ const manifest = loadManifest();
12
+ validateManifest(manifest);
13
+
14
+ const variables = {
15
+ ENTRY_FILES: getEntryFilesForAgent(options.agent).join(', '),
16
+ HARNESS_DIR: options.flat ? '.' : options.harnessDir,
17
+ };
18
+
19
+ console.log(options.dryRun ? 'DRY RUN: preview scaffold changes' : 'Initializing niuma harness');
20
+ console.log(`Workspace: ${workspaceDir}`);
21
+ console.log(`Target: ${targetDir}`);
22
+ console.log(`Agent: ${options.agent}`);
23
+ console.log(`Rules: ${options.rules}`);
24
+
25
+ printAction(ensureDir(targetDir, options.dryRun), targetDir);
26
+
27
+ for (const directory of manifest.directories) {
28
+ const targetPath = safeResolveInside(targetDir, directory, 'directory target');
29
+ printAction(ensureDir(targetPath, options.dryRun), targetPath);
30
+ }
31
+
32
+ for (const entryFile of getEntryFilesForAgent(options.agent)) {
33
+ const templatePath = entryFile === 'CLAUDE.md' ? 'entry/CLAUDE.md' : 'entry/AGENTS.md';
34
+ const targetPath = safeResolveInside(targetDir, entryFile, 'entry target');
35
+ const content = renderTemplate(templatePath, { ...variables, ENTRY_FILE: entryFile });
36
+ printAction(writeFile(targetPath, content, options), targetPath);
37
+ }
38
+
39
+ for (const file of manifest.templateFiles) {
40
+ const targetPath = safeResolveInside(targetDir, file.target, 'template target');
41
+ const content = renderTemplate(file.template, variables);
42
+ printAction(writeFile(targetPath, content, options), targetPath);
43
+ }
44
+
45
+ for (const file of manifest.ruleFiles) {
46
+ const targetPath = safeResolveInside(targetDir, file.target, 'rule target');
47
+ const content = options.rules === 'copy' ? renderTemplate(file.template, variables) : '';
48
+ printAction(writeFile(targetPath, content, options), targetPath);
49
+ }
50
+
51
+ console.log('Done. Read HARNESS_GUIDE.md and fill docs/index.md for your project.');
52
+ }
53
+
54
+ function loadManifest() {
55
+ return JSON.parse(fs.readFileSync(MANIFEST_PATH, 'utf8'));
56
+ }
57
+
58
+ function validateManifest(manifest) {
59
+ for (const directory of manifest.directories || []) {
60
+ validateRelativePath(directory, 'manifest directory');
61
+ }
62
+
63
+ for (const file of [...(manifest.templateFiles || []), ...(manifest.ruleFiles || [])]) {
64
+ validateRelativePath(file.target, 'manifest target');
65
+ validateRelativePath(file.template, 'manifest template');
66
+ safeResolveInside(TEMPLATE_DIR, file.template, 'manifest template');
67
+ }
68
+ }
69
+
70
+ function getEntryFilesForAgent(agent) {
71
+ if (agent === 'claude') {
72
+ return ['CLAUDE.md'];
73
+ }
74
+
75
+ if (agent === 'codex' || agent === 'opencode') {
76
+ return ['AGENTS.md'];
77
+ }
78
+
79
+ if (agent === 'multi') {
80
+ return ['CLAUDE.md', 'AGENTS.md'];
81
+ }
82
+
83
+ throw new Error(`Unsupported agent: ${agent}`);
84
+ }
85
+
86
+ function readTemplate(relativePath) {
87
+ const templatePath = safeResolveInside(TEMPLATE_DIR, relativePath, 'template path');
88
+ return fs.readFileSync(templatePath, 'utf8');
89
+ }
90
+
91
+ function renderTemplate(relativePath, variables) {
92
+ let content = readTemplate(relativePath);
93
+ for (const [key, value] of Object.entries(variables)) {
94
+ content = content.split(`{{${key}}}`).join(value);
95
+ }
96
+ return content;
97
+ }
98
+
99
+ function ensureDir(dirPath, dryRun) {
100
+ assertNoSymlinkInPath(dirPath);
101
+
102
+ if (fs.existsSync(dirPath)) {
103
+ const stat = fs.lstatSync(dirPath);
104
+ if (!stat.isDirectory()) {
105
+ throw new Error(`Path exists but is not a directory: ${dirPath}`);
106
+ }
107
+ return 'skip';
108
+ }
109
+
110
+ if (!dryRun) {
111
+ fs.mkdirSync(dirPath, { recursive: true });
112
+ }
113
+ return 'create';
114
+ }
115
+
116
+ function writeFile(filePath, content, options) {
117
+ assertNoSymlinkInPath(filePath);
118
+
119
+ const exists = fs.existsSync(filePath);
120
+ if (exists && !options.force) {
121
+ return 'skip';
122
+ }
123
+
124
+ if (exists) {
125
+ const stat = fs.lstatSync(filePath);
126
+ if (!stat.isFile()) {
127
+ throw new Error(`Path exists but is not a regular file: ${filePath}`);
128
+ }
129
+ }
130
+
131
+ if (!options.dryRun) {
132
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
133
+ fs.writeFileSync(filePath, content, 'utf8');
134
+ }
135
+
136
+ if (exists && options.force) {
137
+ return 'overwrite';
138
+ }
139
+ return 'create';
140
+ }
141
+
142
+ function validateRelativePath(relativePath, label) {
143
+ if (!relativePath || typeof relativePath !== 'string') {
144
+ throw new Error(`Invalid ${label}: path must be a non-empty string.`);
145
+ }
146
+
147
+ if (path.isAbsolute(relativePath)) {
148
+ throw new Error(`Invalid ${label}: absolute paths are not allowed: ${relativePath}`);
149
+ }
150
+
151
+ const segments = relativePath.split(/[\\/]+/);
152
+ if (segments.includes('..') || segments.includes('')) {
153
+ throw new Error(`Invalid ${label}: path must stay inside the scaffold: ${relativePath}`);
154
+ }
155
+ }
156
+
157
+ function safeResolveInside(baseDir, relativePath, label) {
158
+ validateRelativePath(relativePath, label);
159
+ const base = path.resolve(baseDir);
160
+ const resolved = path.resolve(base, relativePath);
161
+ if (resolved !== base && !resolved.startsWith(`${base}${path.sep}`)) {
162
+ throw new Error(`Invalid ${label}: path escapes base directory: ${relativePath}`);
163
+ }
164
+ return resolved;
165
+ }
166
+
167
+ function assertNoSymlinkInPath(targetPath) {
168
+ const resolved = path.resolve(targetPath);
169
+ const root = path.parse(resolved).root;
170
+ const relative = path.relative(root, resolved);
171
+ const parts = relative ? relative.split(path.sep) : [];
172
+ let current = root;
173
+
174
+ for (const part of parts) {
175
+ current = path.join(current, part);
176
+ if (!fs.existsSync(current)) {
177
+ continue;
178
+ }
179
+
180
+ const stat = fs.lstatSync(current);
181
+ if (stat.isSymbolicLink()) {
182
+ throw new Error(`Refusing to write through symlink: ${current}`);
183
+ }
184
+ }
185
+ }
186
+
187
+ function printAction(action, targetPath) {
188
+ const label = {
189
+ create: 'CREATE',
190
+ overwrite: 'OVERWRITE',
191
+ skip: 'SKIP',
192
+ }[action] || action.toUpperCase();
193
+
194
+ console.log(`${label.padEnd(9)} ${targetPath}`);
195
+ }
196
+
197
+ module.exports = {
198
+ runInit,
199
+ loadManifest,
200
+ validateManifest,
201
+ getEntryFilesForAgent,
202
+ renderTemplate,
203
+ ensureDir,
204
+ writeFile,
205
+ safeResolveInside,
206
+ assertNoSymlinkInPath,
207
+ };
@@ -0,0 +1,35 @@
1
+ # Niuma Harness Guide
2
+
3
+ This directory contains the AI engineering harness for the workspace.
4
+
5
+ Generated entry file(s): `{{ENTRY_FILES}}`
6
+
7
+ ## Purpose
8
+
9
+ The harness gives AI coding tools a stable operating context:
10
+
11
+ - where project code and docs live
12
+ - which rules to follow
13
+ - which process to use for common task types
14
+ - where to record task state and verification evidence
15
+
16
+ ## First steps
17
+
18
+ 1. Fill `docs/index.md` with the real project paths and important docs.
19
+ 2. Fill `docs/project-context.md` with stable project facts.
20
+ 3. Adjust `docs/rules/` to match your team conventions.
21
+ 4. Use `docs/process/` as the default workflow reference.
22
+ 5. Store multi-step task notes in `docs/tasks/`.
23
+
24
+ ## Directory map
25
+
26
+ - `docs/index.md`: project map and reading order.
27
+ - `docs/project-context.md`: stable project context.
28
+ - `docs/rules/`: coding, testing, and security rules.
29
+ - `docs/process/`: reusable task workflows.
30
+ - `docs/automation/`: automation and hook intent notes.
31
+ - `docs/tasks/`: task records and handoff notes.
32
+
33
+ ## Maintenance rule
34
+
35
+ Keep long-lived facts in `docs/project-context.md`. Keep temporary investigation notes in `docs/tasks/` or another task-local location.
@@ -0,0 +1,16 @@
1
+ # Automation and Hook Intent
2
+
3
+ This MVP does not install tool-specific hooks automatically.
4
+
5
+ Use this document to describe automation you may want to map to Claude Code hooks, Codex hooks, opencode plugins, Git hooks, or CI jobs.
6
+
7
+ | Intent | Trigger | Suggested command | Status |
8
+ |---|---|---|---|
9
+ | Format changed files | after file edit | `<fill command>` | planned |
10
+ | Run focused tests | before completion | `<fill command>` | planned |
11
+ | Check secrets | before commit or release | `<fill command>` | planned |
12
+ | Verify build | before release | `<fill command>` | planned |
13
+
14
+ ## Maintenance
15
+
16
+ Keep actual hook configuration in the relevant tool-specific location. Keep this file as the shared source for automation intent.
@@ -0,0 +1,38 @@
1
+ # Harness Index
2
+
3
+ Use this file as the map for AI tools working in this workspace.
4
+
5
+ ## Code locations
6
+
7
+ - Backend: `<fill path, e.g. ../backend>`
8
+ - Frontend: `<fill path, e.g. ../frontend>`
9
+ - Shared packages: `<fill path or N/A>`
10
+
11
+ ## Required reading
12
+
13
+ - Project context: `docs/project-context.md`
14
+ - Task workflows: `docs/process/`
15
+ - Rules: `docs/rules/`
16
+ - Automation notes: `docs/automation/hooks.md`
17
+
18
+ ## Task workflows
19
+
20
+ - Task triage: `docs/process/task-triage.md`
21
+ - Bug fixes: `docs/process/bugfix.md`
22
+ - Feature development: `docs/process/feature-development.md`
23
+
24
+ ## Verification commands
25
+
26
+ Document project-specific commands here:
27
+
28
+ ```bash
29
+ # tests
30
+ <fill command>
31
+
32
+ # lint/typecheck/build
33
+ <fill command>
34
+ ```
35
+
36
+ ## Maintenance
37
+
38
+ Update this index when project paths, important docs, or standard workflows change.
@@ -0,0 +1,19 @@
1
+ # Bugfix Process
2
+
3
+ ## Goal
4
+
5
+ Fix a defect with evidence that the behavior is corrected and existing behavior is not unexpectedly broken.
6
+
7
+ ## Steps
8
+
9
+ 1. Understand the symptom and expected behavior.
10
+ 2. Locate the relevant code path.
11
+ 3. Reproduce the issue with a test, command, or manual steps when practical.
12
+ 4. Implement the smallest safe fix.
13
+ 5. Run focused verification.
14
+ 6. Run broader regression checks when the touched area is shared.
15
+ 7. Report root cause, changed files, verification result, and residual risk.
16
+
17
+ ## Notes
18
+
19
+ If reproduction is not possible in the current environment, document the missing condition and the exact verification steps to run later.
@@ -0,0 +1,29 @@
1
+ # Feature Development Process
2
+
3
+ ## Goal
4
+
5
+ Deliver a feature through clear requirements, implementation notes, and verification evidence.
6
+
7
+ ## Lightweight flow
8
+
9
+ 1. Clarify the goal and acceptance criteria.
10
+ 2. Read project context and relevant existing implementation patterns.
11
+ 3. Plan the smallest safe implementation path.
12
+ 4. Add or update tests for behavior when practical.
13
+ 5. Implement the feature.
14
+ 6. Run relevant verification commands.
15
+ 7. Record what changed, what passed, what failed, and what remains.
16
+
17
+ ## When to pause
18
+
19
+ Pause and ask before:
20
+
21
+ - changing public APIs or data contracts
22
+ - adding dependencies
23
+ - changing authentication, authorization, or security-sensitive behavior
24
+ - making irreversible data changes
25
+ - proceeding when acceptance criteria are unclear
26
+
27
+ ## Task notes
28
+
29
+ For multi-step features, create a task folder under `docs/tasks/<task-name>/` and keep context, plan, and verification notes there.
@@ -0,0 +1,29 @@
1
+ # Task Triage Process
2
+
3
+ Use this process before choosing a more specific workflow.
4
+
5
+ ## 1. Classify the task
6
+
7
+ - Question or explanation only
8
+ - Small edit
9
+ - Bug fix
10
+ - Feature development
11
+ - Refactor
12
+ - Security-sensitive change
13
+ - Documentation update
14
+
15
+ ## 2. Identify required context
16
+
17
+ - Read `docs/index.md`.
18
+ - Read `docs/project-context.md` when design or code changes are involved.
19
+ - Read relevant rules under `docs/rules/`.
20
+
21
+ ## 3. Choose workflow
22
+
23
+ - Bug fix: `docs/process/bugfix.md`
24
+ - Feature development: `docs/process/feature-development.md`
25
+ - Other tasks: use the closest workflow and keep notes in `docs/tasks/` when needed.
26
+
27
+ ## 4. Confirm blockers
28
+
29
+ Ask for clarification when the task changes public contracts, adds dependencies, touches security-sensitive behavior, or lacks enough acceptance criteria.
@@ -0,0 +1,52 @@
1
+ # Project Context
2
+
3
+ This file stores stable facts about the project. Do not use it for temporary investigation notes.
4
+
5
+ ## Metadata
6
+
7
+ - Created:
8
+ - Last updated:
9
+ - Scan scope:
10
+ - Known gaps:
11
+
12
+ ## Project summary
13
+
14
+ Describe the product, main users, and primary business/domain goals.
15
+
16
+ ## Technology stack
17
+
18
+ - Runtime/language:
19
+ - Frameworks:
20
+ - Package manager:
21
+ - Database/storage:
22
+ - Test framework:
23
+
24
+ ## Module structure
25
+
26
+ Document stable module boundaries and important paths.
27
+
28
+ ## Engineering conventions
29
+
30
+ - API response format:
31
+ - Error handling:
32
+ - Auth/security:
33
+ - Logging:
34
+ - Configuration:
35
+
36
+ ## Build and verification commands
37
+
38
+ ```bash
39
+ # install
40
+
41
+ # test
42
+
43
+ # lint/typecheck/build
44
+ ```
45
+
46
+ ## Reference implementations
47
+
48
+ List stable reference modules or features that agents should reuse as patterns.
49
+
50
+ ## Open questions
51
+
52
+ Track project-level questions that cannot be answered from current files.
@@ -0,0 +1,20 @@
1
+ # Coding Style Rules
2
+
3
+ ## Principles
4
+
5
+ - Prefer simple, readable code over clever abstractions.
6
+ - Match the surrounding project style.
7
+ - Make focused changes; do not refactor unrelated code.
8
+ - Reuse existing utilities and patterns before adding new ones.
9
+
10
+ ## Naming
11
+
12
+ - Use descriptive names.
13
+ - Keep files and modules cohesive.
14
+ - Avoid ambiguous abbreviations unless already common in the project.
15
+
16
+ ## Change discipline
17
+
18
+ - Do not introduce new dependencies without explicit approval.
19
+ - Do not change public contracts unless the task requires it and the risk is called out.
20
+ - Remove only dead code introduced by the current change unless asked otherwise.
@@ -0,0 +1,17 @@
1
+ # Security Rules
2
+
3
+ ## Secrets
4
+
5
+ - Never hardcode credentials, tokens, API keys, or private endpoints.
6
+ - Use environment variables or the project's existing secret management pattern.
7
+
8
+ ## Inputs and outputs
9
+
10
+ - Validate input at system boundaries.
11
+ - Avoid unsafe shell execution and path handling.
12
+ - Avoid rendering or logging sensitive data.
13
+
14
+ ## Risk handling
15
+
16
+ Stop and ask before making security-sensitive changes when requirements are unclear.
17
+ Document residual security risk in task notes or verification output.
@@ -0,0 +1,24 @@
1
+ # Testing Rules
2
+
3
+ ## Default expectation
4
+
5
+ Changes should be verified before being called complete.
6
+
7
+ ## Test-first preference
8
+
9
+ For behavior changes:
10
+
11
+ 1. Reproduce the current behavior or failure.
12
+ 2. Add or update a focused test when practical.
13
+ 3. Implement the smallest fix.
14
+ 4. Run the relevant verification commands.
15
+
16
+ ## Reporting
17
+
18
+ Always report:
19
+
20
+ - commands run
21
+ - whether they passed or failed
22
+ - important skipped checks and why
23
+
24
+ Do not claim completion when relevant tests fail unless the user explicitly accepts the remaining risk.
@@ -0,0 +1,15 @@
1
+ # Task Notes
2
+
3
+ Use this directory for multi-step task records, handoffs, and verification notes.
4
+
5
+ Suggested structure:
6
+
7
+ ```text
8
+ docs/tasks/<task-name>/
9
+ context.md
10
+ plan.md
11
+ verification.md
12
+ notes.md
13
+ ```
14
+
15
+ Keep long-lived project facts in `docs/project-context.md`, not here.
@@ -0,0 +1,13 @@
1
+ # Agent Entry
2
+
3
+ This workspace uses a Niuma Harness documentation scaffold.
4
+
5
+ Before starting work:
6
+
7
+ 1. Read `docs/index.md` to understand the project map.
8
+ 2. Read `docs/project-context.md` before design or code changes.
9
+ 3. Follow the relevant process under `docs/process/`.
10
+ 4. Follow shared rules under `docs/rules/`.
11
+ 5. Record task notes under `docs/tasks/` when work spans multiple steps.
12
+
13
+ This file is the generic agent entry point for Codex, opencode, and compatible tools. Treat `docs/` as the source of truth for project context, process, and rules.
@@ -0,0 +1,13 @@
1
+ # Claude Code Entry
2
+
3
+ This workspace uses a Niuma Harness documentation scaffold.
4
+
5
+ Before starting work:
6
+
7
+ 1. Read `docs/index.md` to understand the project map.
8
+ 2. Read `docs/project-context.md` before design or code changes.
9
+ 3. Follow the relevant process under `docs/process/`.
10
+ 4. Follow shared rules under `docs/rules/`.
11
+ 5. Record task notes under `docs/tasks/` when work spans multiple steps.
12
+
13
+ This file is only the Claude Code entry point. Treat `docs/` as the source of truth for project context, process, and rules.
@@ -0,0 +1,58 @@
1
+ {
2
+ "directories": [
3
+ "docs",
4
+ "docs/automation",
5
+ "docs/process",
6
+ "docs/rules",
7
+ "docs/rules/common",
8
+ "docs/tasks"
9
+ ],
10
+ "templateFiles": [
11
+ {
12
+ "target": "HARNESS_GUIDE.md",
13
+ "template": "core/HARNESS_GUIDE.md"
14
+ },
15
+ {
16
+ "target": "docs/index.md",
17
+ "template": "core/docs/index.md"
18
+ },
19
+ {
20
+ "target": "docs/project-context.md",
21
+ "template": "core/docs/project-context.md"
22
+ },
23
+ {
24
+ "target": "docs/process/task-triage.md",
25
+ "template": "core/docs/process/task-triage.md"
26
+ },
27
+ {
28
+ "target": "docs/process/bugfix.md",
29
+ "template": "core/docs/process/bugfix.md"
30
+ },
31
+ {
32
+ "target": "docs/process/feature-development.md",
33
+ "template": "core/docs/process/feature-development.md"
34
+ },
35
+ {
36
+ "target": "docs/automation/hooks.md",
37
+ "template": "core/docs/automation/hooks.md"
38
+ },
39
+ {
40
+ "target": "docs/tasks/README.md",
41
+ "template": "core/docs/tasks/README.md"
42
+ }
43
+ ],
44
+ "ruleFiles": [
45
+ {
46
+ "target": "docs/rules/common/coding-style.md",
47
+ "template": "core/docs/rules/common/coding-style.md"
48
+ },
49
+ {
50
+ "target": "docs/rules/common/testing.md",
51
+ "template": "core/docs/rules/common/testing.md"
52
+ },
53
+ {
54
+ "target": "docs/rules/common/security.md",
55
+ "template": "core/docs/rules/common/security.md"
56
+ }
57
+ ]
58
+ }