@antoneeo/agentic-sdlc-skill 1.6.0 → 1.8.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.
- package/CHANGELOG.md +35 -0
- package/README.md +82 -81
- package/gemini-extension.json +2 -2
- package/package.json +3 -4
- package/scripts/init.js +125 -176
- package/scripts/lib.js +138 -0
- package/scripts/postinstall.js +17 -113
- package/scripts/preuninstall.js +5 -14
- package/skills/agentic-sdlc-skill/ENFORCEMENT.md +56 -55
- package/skills/agentic-sdlc-skill/SKILL.md +263 -172
- package/skills/agentic-sdlc-skill/guides.md +153 -0
- package/skills/agentic-sdlc-skill/scripts/sdlc_check.py +817 -621
- package/skills/agentic-sdlc-skill/templates.md +250 -192
- package/references/analysis_template.md +0 -44
- package/references/architecture_template.md +0 -23
- package/references/existing_features_template.md +0 -8
- package/references/feature_vision_template.md +0 -21
- package/references/features_history_template.md +0 -5
- package/references/principles_template.md +0 -13
- package/references/project_vision_template.md +0 -26
- package/references/roadmap_template.md +0 -9
package/scripts/lib.js
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
// Shared helpers for the Agentic SDLC npm scripts (init / postinstall / preuninstall).
|
|
2
|
+
// Single source for client detection and skill-target paths: init and postinstall
|
|
3
|
+
// must never disagree on what "Claude Code is installed" means.
|
|
4
|
+
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
const os = require('os');
|
|
8
|
+
const { execSync } = require('child_process');
|
|
9
|
+
|
|
10
|
+
const PACKAGE_ROOT = path.resolve(__dirname, '..');
|
|
11
|
+
const SKILL_SOURCE = path.join(PACKAGE_ROOT, 'skills', 'agentic-sdlc-skill');
|
|
12
|
+
const TEMPLATES_PATH = path.join(SKILL_SOURCE, 'templates.md');
|
|
13
|
+
|
|
14
|
+
// One entry per supported AI client. `home` may be overridden by an env var
|
|
15
|
+
// (Claude Desktop / portable installs); presence of the home dir counts as
|
|
16
|
+
// detection even when the CLI is not on PATH.
|
|
17
|
+
const CLIENTS = [
|
|
18
|
+
{
|
|
19
|
+
key: 'claude',
|
|
20
|
+
label: 'Claude Code',
|
|
21
|
+
cmd: 'claude',
|
|
22
|
+
home: process.env.CLAUDE_CONFIG_DIR || path.join(os.homedir(), '.claude'),
|
|
23
|
+
envVar: 'CLAUDE_CONFIG_DIR',
|
|
24
|
+
reload: 'Restart Claude Code to load it. Invoke via Skill tool as "agentic-sdlc".',
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
key: 'gemini',
|
|
28
|
+
label: 'Gemini CLI',
|
|
29
|
+
cmd: 'gemini',
|
|
30
|
+
home: process.env.GEMINI_HOME || path.join(os.homedir(), '.gemini'),
|
|
31
|
+
envVar: 'GEMINI_HOME',
|
|
32
|
+
reload: 'Run "gemini skills reload" or restart Gemini CLI to load it.',
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
key: 'codex',
|
|
36
|
+
label: 'Codex AI',
|
|
37
|
+
cmd: 'codex',
|
|
38
|
+
home: process.env.CODEX_HOME || path.join(os.homedir(), '.codex'),
|
|
39
|
+
envVar: 'CODEX_HOME',
|
|
40
|
+
reload: 'Restart Codex to load it. Invoke it as "$agentic-sdlc" or by asking for Agentic SDLC.',
|
|
41
|
+
},
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
function commandExists(cmd) {
|
|
45
|
+
try {
|
|
46
|
+
execSync(`${cmd} --version`, { stdio: 'ignore' });
|
|
47
|
+
return true;
|
|
48
|
+
} catch (e) {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function clientDetected(client) {
|
|
54
|
+
return commandExists(client.cmd)
|
|
55
|
+
|| Boolean(process.env[client.envVar])
|
|
56
|
+
|| fs.existsSync(client.home);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function skillTarget(client) {
|
|
60
|
+
return path.join(client.home, 'skills', 'agentic-sdlc');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function copyRecursive(src, dest) {
|
|
64
|
+
if (typeof fs.cpSync === 'function') {
|
|
65
|
+
fs.cpSync(src, dest, { recursive: true, force: true });
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
// Fallback for Node < 16.7
|
|
69
|
+
if (!fs.existsSync(dest)) fs.mkdirSync(dest, { recursive: true });
|
|
70
|
+
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
71
|
+
const s = path.join(src, entry.name);
|
|
72
|
+
const d = path.join(dest, entry.name);
|
|
73
|
+
if (entry.isDirectory()) copyRecursive(s, d);
|
|
74
|
+
else fs.copyFileSync(s, d);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Parse templates.md into { headingText: [fencedBlock, ...] }.
|
|
80
|
+
* Templates are single-sourced there: the init script must extract them
|
|
81
|
+
* instead of carrying its own inline copies (which historically drifted).
|
|
82
|
+
*/
|
|
83
|
+
function loadTemplates() {
|
|
84
|
+
const text = fs.readFileSync(TEMPLATES_PATH, 'utf8');
|
|
85
|
+
const lines = text.split(/\r?\n/);
|
|
86
|
+
const sections = {};
|
|
87
|
+
let heading = null;
|
|
88
|
+
let block = null;
|
|
89
|
+
for (const line of lines) {
|
|
90
|
+
const h = line.match(/^##\s+(.*)$/);
|
|
91
|
+
if (h && block === null) {
|
|
92
|
+
heading = h[1].trim();
|
|
93
|
+
sections[heading] = sections[heading] || [];
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
if (/^```/.test(line)) {
|
|
97
|
+
if (block === null) {
|
|
98
|
+
block = [];
|
|
99
|
+
} else {
|
|
100
|
+
if (heading) sections[heading].push(block.join('\n') + '\n');
|
|
101
|
+
block = null;
|
|
102
|
+
}
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
if (block !== null) block.push(line);
|
|
106
|
+
}
|
|
107
|
+
return sections;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Return the Nth fenced block of the section whose heading contains `needle`.
|
|
112
|
+
* Throws with a clear message when missing: writing a wrong or empty
|
|
113
|
+
* boilerplate silently would be worse than failing the init.
|
|
114
|
+
*/
|
|
115
|
+
function templateFor(sections, needle, index = 0) {
|
|
116
|
+
const heading = Object.keys(sections).find((h) => h.includes(needle));
|
|
117
|
+
const blocks = heading ? sections[heading] : undefined;
|
|
118
|
+
if (!blocks || !blocks[index]) {
|
|
119
|
+
throw new Error(
|
|
120
|
+
`Template section containing "${needle}" (block ${index}) not found in ${TEMPLATES_PATH}. ` +
|
|
121
|
+
'The package is corrupted or templates.md was restructured: fix templates.md, do not improvise content.'
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
return blocks[index];
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
module.exports = {
|
|
128
|
+
PACKAGE_ROOT,
|
|
129
|
+
SKILL_SOURCE,
|
|
130
|
+
TEMPLATES_PATH,
|
|
131
|
+
CLIENTS,
|
|
132
|
+
commandExists,
|
|
133
|
+
clientDetected,
|
|
134
|
+
skillTarget,
|
|
135
|
+
copyRecursive,
|
|
136
|
+
loadTemplates,
|
|
137
|
+
templateFor,
|
|
138
|
+
};
|
package/scripts/postinstall.js
CHANGED
|
@@ -1,132 +1,36 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
1
|
+
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
const { execSync } = require('child_process');
|
|
4
3
|
const fs = require('fs');
|
|
5
|
-
const
|
|
6
|
-
const os = require('os');
|
|
4
|
+
const { SKILL_SOURCE, CLIENTS, clientDetected, skillTarget, copyRecursive } = require('./lib');
|
|
7
5
|
|
|
8
|
-
|
|
9
|
-
const SKILL_SOURCE = path.join(PACKAGE_ROOT, 'skills', 'agentic-sdlc-skill');
|
|
10
|
-
const CLAUDE_HOME = process.env.CLAUDE_CONFIG_DIR || path.join(os.homedir(), '.claude');
|
|
11
|
-
const CLAUDE_SKILLS_DIR = path.join(CLAUDE_HOME, 'skills');
|
|
12
|
-
const CLAUDE_SKILL_TARGET = path.join(CLAUDE_SKILLS_DIR, 'agentic-sdlc');
|
|
13
|
-
const CODEX_HOME = process.env.CODEX_HOME || path.join(os.homedir(), '.codex');
|
|
14
|
-
const CODEX_SKILLS_DIR = path.join(CODEX_HOME, 'skills');
|
|
15
|
-
const CODEX_SKILL_TARGET = path.join(CODEX_SKILLS_DIR, 'agentic-sdlc');
|
|
16
|
-
const GEMINI_HOME = process.env.GEMINI_HOME || path.join(os.homedir(), '.gemini');
|
|
17
|
-
const GEMINI_SKILLS_DIR = path.join(GEMINI_HOME, 'skills');
|
|
18
|
-
const GEMINI_SKILL_TARGET = path.join(GEMINI_SKILLS_DIR, 'agentic-sdlc');
|
|
19
|
-
|
|
20
|
-
function checkCommand(cmd) {
|
|
21
|
-
try {
|
|
22
|
-
execSync(`${cmd} --version`, { stdio: 'ignore' });
|
|
23
|
-
return true;
|
|
24
|
-
} catch (e) {
|
|
25
|
-
return false;
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function copyRecursive(src, dest) {
|
|
30
|
-
if (typeof fs.cpSync === 'function') {
|
|
31
|
-
fs.cpSync(src, dest, { recursive: true, force: true });
|
|
32
|
-
return;
|
|
33
|
-
}
|
|
34
|
-
// Fallback for Node < 16.7
|
|
35
|
-
if (!fs.existsSync(dest)) fs.mkdirSync(dest, { recursive: true });
|
|
36
|
-
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
37
|
-
const s = path.join(src, entry.name);
|
|
38
|
-
const d = path.join(dest, entry.name);
|
|
39
|
-
if (entry.isDirectory()) copyRecursive(s, d);
|
|
40
|
-
else fs.copyFileSync(s, d);
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function installClaudeSkill() {
|
|
45
|
-
if (!fs.existsSync(SKILL_SOURCE)) {
|
|
46
|
-
console.log(`⚠️ Skill source not found at ${SKILL_SOURCE}; skipping Claude Code install.`);
|
|
47
|
-
return false;
|
|
48
|
-
}
|
|
49
|
-
try {
|
|
50
|
-
fs.mkdirSync(CLAUDE_SKILLS_DIR, { recursive: true });
|
|
51
|
-
copyRecursive(SKILL_SOURCE, CLAUDE_SKILL_TARGET);
|
|
52
|
-
console.log(`📦 Installed Claude Code skill at: ${CLAUDE_SKILL_TARGET}`);
|
|
53
|
-
console.log(' Restart Claude Code to load it. Invoke via Skill tool as "agentic-sdlc".');
|
|
54
|
-
return true;
|
|
55
|
-
} catch (err) {
|
|
56
|
-
console.log(`⚠️ Failed to install Claude Code skill: ${err.message}`);
|
|
57
|
-
console.log(` Manual install: copy "${SKILL_SOURCE}" to "${CLAUDE_SKILL_TARGET}".`);
|
|
58
|
-
return false;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function installCodexSkill() {
|
|
6
|
+
function installSkill(client) {
|
|
63
7
|
if (!fs.existsSync(SKILL_SOURCE)) {
|
|
64
|
-
console.log(`⚠️ Skill source not found at ${SKILL_SOURCE}; skipping
|
|
8
|
+
console.log(`⚠️ Skill source not found at ${SKILL_SOURCE}; skipping ${client.label} install.`);
|
|
65
9
|
return false;
|
|
66
10
|
}
|
|
11
|
+
const target = skillTarget(client);
|
|
67
12
|
try {
|
|
68
|
-
fs.mkdirSync(
|
|
69
|
-
copyRecursive(SKILL_SOURCE,
|
|
70
|
-
console.log(`📦 Installed
|
|
71
|
-
console.log(
|
|
13
|
+
fs.mkdirSync(target, { recursive: true });
|
|
14
|
+
copyRecursive(SKILL_SOURCE, target);
|
|
15
|
+
console.log(`📦 Installed ${client.label} skill at: ${target}`);
|
|
16
|
+
console.log(` ${client.reload}`);
|
|
72
17
|
return true;
|
|
73
18
|
} catch (err) {
|
|
74
|
-
console.log(`⚠️ Failed to install
|
|
75
|
-
console.log(` Manual install: copy "${SKILL_SOURCE}" to "${
|
|
19
|
+
console.log(`⚠️ Failed to install ${client.label} skill: ${err.message}`);
|
|
20
|
+
console.log(` Manual install: copy "${SKILL_SOURCE}" to "${target}".`);
|
|
76
21
|
return false;
|
|
77
22
|
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function installGeminiSkill() {
|
|
81
|
-
if (!fs.existsSync(SKILL_SOURCE)) {
|
|
82
|
-
console.log(`⚠️ Skill source not found at ${SKILL_SOURCE}; skipping Gemini install.`);
|
|
83
|
-
return false;
|
|
84
|
-
}
|
|
85
|
-
try {
|
|
86
|
-
fs.mkdirSync(GEMINI_SKILLS_DIR, { recursive: true });
|
|
87
|
-
copyRecursive(SKILL_SOURCE, GEMINI_SKILL_TARGET);
|
|
88
|
-
console.log(`📦 Installed Gemini skill at: ${GEMINI_SKILL_TARGET}`);
|
|
89
|
-
console.log(' Run "gemini skills reload" or restart Gemini CLI to load it.');
|
|
90
|
-
return true;
|
|
91
|
-
} catch (err) {
|
|
92
|
-
console.log(`⚠️ Failed to install Gemini skill: ${err.message}`);
|
|
93
|
-
console.log(` Manual install: copy "${SKILL_SOURCE}" to "${GEMINI_SKILL_TARGET}".`);
|
|
94
|
-
return false;
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
function hasCodexHome() {
|
|
99
|
-
return Boolean(process.env.CODEX_HOME) || fs.existsSync(CODEX_HOME);
|
|
100
23
|
}
|
|
101
24
|
|
|
102
|
-
function hasClaudeHome() {
|
|
103
|
-
return Boolean(process.env.CLAUDE_CONFIG_DIR) || fs.existsSync(CLAUDE_HOME);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
function hasGeminiHome() {
|
|
107
|
-
return Boolean(process.env.GEMINI_HOME) || fs.existsSync(GEMINI_HOME);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
25
|
console.log('\n--- Agentic SDLC Skill Discovery ---');
|
|
111
26
|
|
|
112
27
|
let detected = false;
|
|
113
|
-
|
|
114
|
-
if (
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
if (checkCommand('gemini') || hasGeminiHome()) {
|
|
121
|
-
console.log(`✅ Detected: Gemini CLI`);
|
|
122
|
-
detected = true;
|
|
123
|
-
installGeminiSkill();
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
if (checkCommand('codex') || hasCodexHome()) {
|
|
127
|
-
console.log(`✅ Detected: Codex AI`);
|
|
128
|
-
detected = true;
|
|
129
|
-
installCodexSkill();
|
|
28
|
+
for (const client of CLIENTS) {
|
|
29
|
+
if (clientDetected(client)) {
|
|
30
|
+
console.log(`✅ Detected: ${client.label}`);
|
|
31
|
+
detected = true;
|
|
32
|
+
installSkill(client);
|
|
33
|
+
}
|
|
130
34
|
}
|
|
131
35
|
|
|
132
36
|
if (!detected) {
|
package/scripts/preuninstall.js
CHANGED
|
@@ -1,26 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
const fs = require('fs');
|
|
4
|
-
const
|
|
5
|
-
const os = require('os');
|
|
4
|
+
const { CLIENTS, skillTarget } = require('./lib');
|
|
6
5
|
|
|
7
|
-
|
|
8
|
-
const
|
|
9
|
-
const CODEX_HOME = process.env.CODEX_HOME || path.join(os.homedir(), '.codex');
|
|
10
|
-
const CODEX_SKILL_TARGET = path.join(CODEX_HOME, 'skills', 'agentic-sdlc');
|
|
11
|
-
const GEMINI_HOME = process.env.GEMINI_HOME || path.join(os.homedir(), '.gemini');
|
|
12
|
-
const GEMINI_SKILL_TARGET = path.join(GEMINI_HOME, 'skills', 'agentic-sdlc');
|
|
13
|
-
|
|
14
|
-
function removeSkill(target, label) {
|
|
6
|
+
function removeSkill(client) {
|
|
7
|
+
const target = skillTarget(client);
|
|
15
8
|
if (!fs.existsSync(target)) return;
|
|
16
9
|
try {
|
|
17
10
|
fs.rmSync(target, { recursive: true, force: true });
|
|
18
|
-
console.log(`🧹 Removed ${label} skill at: ${target}`);
|
|
11
|
+
console.log(`🧹 Removed ${client.label} skill at: ${target}`);
|
|
19
12
|
} catch (err) {
|
|
20
13
|
console.log(`⚠️ Could not remove ${target}: ${err.message}`);
|
|
21
14
|
}
|
|
22
15
|
}
|
|
23
16
|
|
|
24
|
-
removeSkill
|
|
25
|
-
removeSkill(CODEX_SKILL_TARGET, 'Codex');
|
|
26
|
-
removeSkill(GEMINI_SKILL_TARGET, 'Gemini');
|
|
17
|
+
CLIENTS.forEach(removeSkill);
|
|
@@ -1,55 +1,56 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
## 1.
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
```
|
|
10
|
-
python "<
|
|
11
|
-
```
|
|
12
|
-
|
|
13
|
-
(`check` = validate + stale in
|
|
14
|
-
|
|
15
|
-
## 2. Check in CI (
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
```
|
|
20
|
-
python tools/sdlc_check.py validate
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
## 3.
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
```json
|
|
32
|
-
{
|
|
33
|
-
"hooks": {
|
|
34
|
-
"PreToolUse": [
|
|
35
|
-
{
|
|
36
|
-
"matcher": "Write|Edit",
|
|
37
|
-
"hooks": [
|
|
38
|
-
{
|
|
39
|
-
"type": "command",
|
|
40
|
-
"command": "python \"C:\\Users\\<
|
|
41
|
-
}
|
|
42
|
-
]
|
|
43
|
-
}
|
|
44
|
-
]
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
**
|
|
52
|
-
-
|
|
53
|
-
-
|
|
54
|
-
- `ai_docs/`, `tests/`
|
|
55
|
-
-
|
|
1
|
+
# Mechanical enforcement (optional, recommended for teams)
|
|
2
|
+
|
|
3
|
+
Prompt-level rules depend on the model's discipline and degrade with long contexts, compaction and competing instructions. Three levels of increasing guarantee:
|
|
4
|
+
|
|
5
|
+
## 1. Interactive validation (default, no setup)
|
|
6
|
+
|
|
7
|
+
The agent runs a single gate at closure (Phase 5):
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
python "<skill_dir>/scripts/sdlc_check.py" check
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
(`check` = validate + stale in one command.) Exit code ≠ 0 ⇒ the feature is not declared closed. This is the minimum level the skill expects.
|
|
14
|
+
|
|
15
|
+
## 2. Check in CI (recommended for teams)
|
|
16
|
+
|
|
17
|
+
Copy `scripts/sdlc_check.py` into the repository (e.g. `tools/sdlc_check.py`) and add to the pipeline:
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
python tools/sdlc_check.py validate --strict
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Effect: an unregenerated index, invalid frontmatter, a missing security section or incoherent states **block the pipeline** instead of relying on the agent's memory. `--strict` also fails on warnings and on a missing `ai_docs/`, so a wrong working directory cannot produce a green pipeline. This works because documents travel in the same PR as the code (Phase 5 rule).
|
|
24
|
+
|
|
25
|
+
Note: the copy in the repo is the authoritative one for CI; update it when you update the skill.
|
|
26
|
+
|
|
27
|
+
## 3. PreToolUse hook (gate on writes)
|
|
28
|
+
|
|
29
|
+
Blocks Edit/Write on protected paths when no `ANALYSIS_*.md` is `IN_PROGRESS`. In the project's `.claude/settings.json`:
|
|
30
|
+
|
|
31
|
+
```json
|
|
32
|
+
{
|
|
33
|
+
"hooks": {
|
|
34
|
+
"PreToolUse": [
|
|
35
|
+
{
|
|
36
|
+
"matcher": "Write|Edit",
|
|
37
|
+
"hooks": [
|
|
38
|
+
{
|
|
39
|
+
"type": "command",
|
|
40
|
+
"command": "python \"C:\\Users\\<user>\\.claude\\skills\\agentic-sdlc\\scripts\\sdlc_check.py\" gate --hook --protected \"src/auth;src/crypto\""
|
|
41
|
+
}
|
|
42
|
+
]
|
|
43
|
+
}
|
|
44
|
+
]
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Semantics: exit code 2 + message on stderr ⇒ the write is blocked and the message is shown to the agent, which must create the ANALYSIS (Phase 3) before retrying.
|
|
50
|
+
|
|
51
|
+
**Usage warnings:**
|
|
52
|
+
- The gate is deliberately coarse: applied to all of `src/` it would also block the legitimate L1/L2 tasks foreseen by the Triage. Use it **only on security-critical directories** (`--protected "src/auth;src/crypto"`), where "never without analysis" is the desired policy.
|
|
53
|
+
- The paths in `--protected` are prefixes relative to the project root, separated by `;`.
|
|
54
|
+
- `ai_docs/`, `tests/` and `test/` are always excluded from blocking.
|
|
55
|
+
- The hook assumes the working directory is the project root (standard behavior of Claude Code hooks).
|
|
56
|
+
- **Hybrid/devPNT projects**: add `--hybrid` to the gate command. Governed designs live in the devPNT DB, so the gate also unlocks when an approved E-TDD shadow (`ai_docs/solutions/SHADOW_*tdd*.md`, exported before implementation — see the SKILL.md shadow discipline) is present. Without the flag the gate would block legitimate governed work. The flag is deliberately explicit: never auto-detected.
|