@wipcomputer/wip-repo-init 1.0.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/SKILL.md +77 -0
- package/init.mjs +142 -0
- package/package.json +11 -0
package/SKILL.md
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: wip-repo-init
|
|
3
|
+
description: Scaffold the standard ai/ directory structure in any repo.
|
|
4
|
+
license: MIT
|
|
5
|
+
interface: [cli, skill]
|
|
6
|
+
metadata:
|
|
7
|
+
display-name: "Repo Init"
|
|
8
|
+
version: "1.0.0"
|
|
9
|
+
homepage: "https://github.com/wipcomputer/wip-ai-devops-toolbox"
|
|
10
|
+
author: "Parker Todd Brooks"
|
|
11
|
+
category: repo-management
|
|
12
|
+
capabilities:
|
|
13
|
+
- scaffold-ai-dir
|
|
14
|
+
- template-copy
|
|
15
|
+
requires:
|
|
16
|
+
bins: [node]
|
|
17
|
+
openclaw:
|
|
18
|
+
requires:
|
|
19
|
+
bins: [node]
|
|
20
|
+
install:
|
|
21
|
+
- id: node
|
|
22
|
+
kind: node
|
|
23
|
+
package: "@wipcomputer/wip-repo-init"
|
|
24
|
+
bins: [wip-repo-init]
|
|
25
|
+
label: "Install via npm"
|
|
26
|
+
emoji: "📁"
|
|
27
|
+
compatibility: Requires node. Node.js 18+.
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
# Repo Init
|
|
31
|
+
|
|
32
|
+
Scaffolds the standard `ai/` directory structure in any repo.
|
|
33
|
+
|
|
34
|
+
## Commands
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
wip-repo-init /path/to/repo # scaffold ai/ in a repo
|
|
38
|
+
wip-repo-init /path/to/repo --dry-run # preview without changes
|
|
39
|
+
wip-repo-init /path/to/repo --yes # skip confirmation prompt
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## What happens
|
|
43
|
+
|
|
44
|
+
**New repo (no ai/ folder):** Creates the full standard structure with all READMEs explaining what goes where.
|
|
45
|
+
|
|
46
|
+
**Existing repo (ai/ folder exists):** Shows you what will happen and asks for confirmation. If you say yes:
|
|
47
|
+
1. Moves your current `ai/` contents to `ai/_sort/ai_old/`
|
|
48
|
+
2. Scaffolds the new standard structure
|
|
49
|
+
3. You sort files from `ai_old/` into the new structure at your own pace
|
|
50
|
+
|
|
51
|
+
Nothing is deleted. Your old files are all in `ai/_sort/ai_old/`.
|
|
52
|
+
|
|
53
|
+
## The standard ai/ structure
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
ai/
|
|
57
|
+
read-me-first.md <- explains everything, links to all sections
|
|
58
|
+
_sort/ <- holding pen for files that need sorting
|
|
59
|
+
_trash/ <- archive (never delete, move here)
|
|
60
|
+
dev-updates/ <- engineering changelog, auto-detected by wip-release
|
|
61
|
+
product/
|
|
62
|
+
readme-first-product.md <- the product bible
|
|
63
|
+
notes/ <- freeform notes, research
|
|
64
|
+
plans-prds/ <- plans with lifecycle stages
|
|
65
|
+
roadmap.md <- prioritized roadmap
|
|
66
|
+
current/ <- plans being built now
|
|
67
|
+
upcoming/ <- plans that are next
|
|
68
|
+
archive-complete/ <- plans that shipped
|
|
69
|
+
todos/ <- per-agent todo files
|
|
70
|
+
product-ideas/ <- ideas that aren't plans yet
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Every folder has a `_trash/` subfolder. Every section has a README explaining what it is, what goes in it, and how to maintain it.
|
|
74
|
+
|
|
75
|
+
## Interfaces
|
|
76
|
+
|
|
77
|
+
CLI, Skill
|
package/init.mjs
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// wip-repo-init: scaffold the standard ai/ directory structure in any repo.
|
|
3
|
+
//
|
|
4
|
+
// New repo (no ai/ folder):
|
|
5
|
+
// Copies the template as-is.
|
|
6
|
+
//
|
|
7
|
+
// Existing repo (ai/ folder exists):
|
|
8
|
+
// 1. Creates the new ai/ structure
|
|
9
|
+
// 2. Moves old ai/ contents into ai/_sort/ai_old/
|
|
10
|
+
// 3. You sort from there at your own pace.
|
|
11
|
+
|
|
12
|
+
import { existsSync, mkdirSync, cpSync, renameSync, readdirSync, statSync } from 'node:fs';
|
|
13
|
+
import { join, resolve, dirname } from 'node:path';
|
|
14
|
+
import { fileURLToPath } from 'node:url';
|
|
15
|
+
import { createInterface } from 'node:readline';
|
|
16
|
+
|
|
17
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
18
|
+
const TEMPLATE = join(__dirname, 'ai');
|
|
19
|
+
|
|
20
|
+
const targetRepo = resolve(process.argv[2] || process.cwd());
|
|
21
|
+
const aiDir = join(targetRepo, 'ai');
|
|
22
|
+
const dryRun = process.argv.includes('--dry-run');
|
|
23
|
+
|
|
24
|
+
const forceYes = process.argv.includes('--yes') || process.argv.includes('-y');
|
|
25
|
+
|
|
26
|
+
function log(msg) { console.log(` ${msg}`); }
|
|
27
|
+
function ok(msg) { console.log(` ✓ ${msg}`); }
|
|
28
|
+
function skip(msg) { console.log(` - ${msg}`); }
|
|
29
|
+
|
|
30
|
+
async function confirm(question) {
|
|
31
|
+
if (forceYes) return true;
|
|
32
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
33
|
+
return new Promise(resolve => {
|
|
34
|
+
rl.question(` ${question} (y/N) `, answer => {
|
|
35
|
+
rl.close();
|
|
36
|
+
resolve(answer.trim().toLowerCase() === 'y');
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Recursively copy template, skipping files that already exist
|
|
42
|
+
function scaffoldDir(src, dest) {
|
|
43
|
+
if (!existsSync(dest)) {
|
|
44
|
+
if (!dryRun) mkdirSync(dest, { recursive: true });
|
|
45
|
+
ok(`Created ${dest.replace(targetRepo + '/', '')}`);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
for (const entry of readdirSync(src)) {
|
|
49
|
+
if (entry === '.DS_Store') continue;
|
|
50
|
+
const srcPath = join(src, entry);
|
|
51
|
+
const destPath = join(dest, entry);
|
|
52
|
+
const stat = statSync(srcPath);
|
|
53
|
+
|
|
54
|
+
if (stat.isDirectory()) {
|
|
55
|
+
scaffoldDir(srcPath, destPath);
|
|
56
|
+
} else {
|
|
57
|
+
if (existsSync(destPath)) {
|
|
58
|
+
skip(`${destPath.replace(targetRepo + '/', '')} already exists`);
|
|
59
|
+
} else {
|
|
60
|
+
if (!dryRun) cpSync(srcPath, destPath);
|
|
61
|
+
ok(`${destPath.replace(targetRepo + '/', '')}`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
console.log('');
|
|
68
|
+
console.log(` wip-repo-init${dryRun ? ' (dry run)' : ''}`);
|
|
69
|
+
console.log(` Target: ${targetRepo}`);
|
|
70
|
+
console.log(` ${'─'.repeat(40)}`);
|
|
71
|
+
|
|
72
|
+
if (!existsSync(targetRepo)) {
|
|
73
|
+
console.log(` ✗ Target directory does not exist: ${targetRepo}`);
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (existsSync(aiDir)) {
|
|
78
|
+
// Existing ai/ folder: explain what will happen, then confirm
|
|
79
|
+
log('Found existing ai/ folder.');
|
|
80
|
+
console.log('');
|
|
81
|
+
log('Here\'s what will happen:');
|
|
82
|
+
log(' 1. Your current ai/ contents will be moved to ai/_sort/ai_old/');
|
|
83
|
+
log(' 2. The standard ai/ structure will be scaffolded');
|
|
84
|
+
log(' 3. You can sort files from ai_old/ into the new structure at your own pace');
|
|
85
|
+
console.log('');
|
|
86
|
+
log('Nothing is deleted. Your old files will all be in ai/_sort/ai_old/.');
|
|
87
|
+
console.log('');
|
|
88
|
+
|
|
89
|
+
const sortDir = join(aiDir, '_sort');
|
|
90
|
+
const aiOldDir = join(sortDir, 'ai_old');
|
|
91
|
+
|
|
92
|
+
if (existsSync(aiOldDir)) {
|
|
93
|
+
console.log(` ✗ ai/_sort/ai_old/ already exists. A previous init was run but not sorted yet.`);
|
|
94
|
+
console.log(` Sort the files in ai/_sort/ai_old/ first, then run again.`);
|
|
95
|
+
process.exit(1);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (dryRun) {
|
|
99
|
+
log('[dry run] Would move old ai/ contents to ai/_sort/ai_old/');
|
|
100
|
+
log('[dry run] Would scaffold new ai/ structure:');
|
|
101
|
+
console.log('');
|
|
102
|
+
scaffoldDir(TEMPLATE, aiDir);
|
|
103
|
+
console.log(`\n ${'─'.repeat(40)}`);
|
|
104
|
+
console.log(' Dry run complete. No changes made.\n');
|
|
105
|
+
process.exit(0);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const proceed = await confirm('Proceed?');
|
|
109
|
+
if (!proceed) {
|
|
110
|
+
console.log(' Cancelled.\n');
|
|
111
|
+
process.exit(0);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
console.log('');
|
|
115
|
+
const tmpOld = join(targetRepo, '_ai_old_tmp');
|
|
116
|
+
renameSync(aiDir, tmpOld);
|
|
117
|
+
ok('Moved old ai/ to temporary location');
|
|
118
|
+
|
|
119
|
+
scaffoldDir(TEMPLATE, aiDir);
|
|
120
|
+
|
|
121
|
+
mkdirSync(join(aiDir, '_sort'), { recursive: true });
|
|
122
|
+
renameSync(tmpOld, aiOldDir);
|
|
123
|
+
ok('Moved old ai/ contents to ai/_sort/ai_old/');
|
|
124
|
+
} else {
|
|
125
|
+
// No ai/ folder: scaffold from scratch
|
|
126
|
+
log('No existing ai/ folder. Scaffolding from template.');
|
|
127
|
+
console.log('');
|
|
128
|
+
scaffoldDir(TEMPLATE, aiDir);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
console.log(` ${'─'.repeat(40)}`);
|
|
132
|
+
if (dryRun) {
|
|
133
|
+
console.log(' Dry run complete. No changes made.');
|
|
134
|
+
} else {
|
|
135
|
+
ok('Done.');
|
|
136
|
+
if (existsSync(join(aiDir, '_sort', 'ai_old'))) {
|
|
137
|
+
console.log('');
|
|
138
|
+
log('Your old ai/ contents are in ai/_sort/ai_old/');
|
|
139
|
+
log('Sort them into the new structure at your own pace.');
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
console.log('');
|
package/package.json
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@wipcomputer/wip-repo-init",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Scaffold the standard ai/ directory structure in any repo",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"wip-repo-init": "init.mjs"
|
|
8
|
+
},
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"author": "WIP Computer"
|
|
11
|
+
}
|