projectstore-codex 0.0.1 → 0.28.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.codex-plugin/plugin.json +48 -0
- package/README.md +15 -7
- package/bin/projectstore-codex.mjs +88 -0
- package/hooks/hooks.json +59 -0
- package/node_modules/projectstore/.claude-plugin/marketplace.json +40 -0
- package/node_modules/projectstore/.claude-plugin/plugin.json +23 -0
- package/node_modules/projectstore/.mcp.json +14 -0
- package/node_modules/projectstore/AGENTS.md +26 -0
- package/node_modules/projectstore/LICENSE +21 -0
- package/node_modules/projectstore/README.md +284 -0
- package/node_modules/projectstore/agents/archaeologist.md +76 -0
- package/node_modules/projectstore/agents/clerk.md +93 -0
- package/node_modules/projectstore/agents/critic.md +94 -0
- package/node_modules/projectstore/agents/librarian.md +81 -0
- package/node_modules/projectstore/agents/planner.md +80 -0
- package/node_modules/projectstore/agents/reviewer.md +98 -0
- package/node_modules/projectstore/bin/projectstore.mjs +7 -0
- package/node_modules/projectstore/commands/adr.md +57 -0
- package/node_modules/projectstore/commands/agents.md +180 -0
- package/node_modules/projectstore/commands/bind.md +128 -0
- package/node_modules/projectstore/commands/codemap.md +50 -0
- package/node_modules/projectstore/commands/concept.md +17 -0
- package/node_modules/projectstore/commands/doctor.md +166 -0
- package/node_modules/projectstore/commands/epic.md +40 -0
- package/node_modules/projectstore/commands/graph.md +56 -0
- package/node_modules/projectstore/commands/kanban.md +40 -0
- package/node_modules/projectstore/commands/meeting.md +17 -0
- package/node_modules/projectstore/commands/reconcile.md +73 -0
- package/node_modules/projectstore/commands/research.md +17 -0
- package/node_modules/projectstore/commands/review.md +89 -0
- package/node_modules/projectstore/commands/runbook.md +17 -0
- package/node_modules/projectstore/commands/scaffold.md +23 -0
- package/node_modules/projectstore/commands/search.md +22 -0
- package/node_modules/projectstore/commands/spec.md +91 -0
- package/node_modules/projectstore/commands/status.md +27 -0
- package/node_modules/projectstore/commands/statusline.md +46 -0
- package/node_modules/projectstore/commands/story.md +113 -0
- package/node_modules/projectstore/docs/extending.md +172 -0
- package/node_modules/projectstore/docs/getting-started.md +133 -0
- package/node_modules/projectstore/docs/harnesses.md +176 -0
- package/node_modules/projectstore/docs/how-it-works.md +263 -0
- package/node_modules/projectstore/docs/images/loop-light.svg +94 -0
- package/node_modules/projectstore/docs/images/loop.svg +93 -0
- package/node_modules/projectstore/docs/images/statusline-hud.png +0 -0
- package/node_modules/projectstore/docs/images/team-light.svg +79 -0
- package/node_modules/projectstore/docs/images/team.svg +79 -0
- package/node_modules/projectstore/harnesses/claude-code.json +483 -0
- package/node_modules/projectstore/harnesses/codex.json +332 -0
- package/node_modules/projectstore/hooks/hooks.json +59 -0
- package/node_modules/projectstore/hooks/pre-compact.mjs +121 -0
- package/node_modules/projectstore/hooks/session-rules.mjs +63 -0
- package/node_modules/projectstore/hooks/session-start.mjs +301 -0
- package/node_modules/projectstore/hooks/session-stop.mjs +84 -0
- package/node_modules/projectstore/package.json +70 -0
- package/node_modules/projectstore/scaffold/checklists.json +88 -0
- package/node_modules/projectstore/scaffold/headings.json +171 -0
- package/node_modules/projectstore/scaffold/layouts/engineering.json +85 -0
- package/node_modules/projectstore/scripts/binding.mjs +165 -0
- package/node_modules/projectstore/scripts/build-adapters.mjs +264 -0
- package/node_modules/projectstore/scripts/cli.mjs +595 -0
- package/node_modules/projectstore/scripts/codemap.mjs +99 -0
- package/node_modules/projectstore/scripts/diff-refs.mjs +127 -0
- package/node_modules/projectstore/scripts/doctor.mjs +2127 -0
- package/node_modules/projectstore/scripts/draft.mjs +261 -0
- package/node_modules/projectstore/scripts/graph.mjs +219 -0
- package/node_modules/projectstore/scripts/harness.mjs +608 -0
- package/node_modules/projectstore/scripts/install-harness.mjs +1387 -0
- package/node_modules/projectstore/scripts/kanban.mjs +174 -0
- package/node_modules/projectstore/scripts/lib.mjs +3085 -0
- package/node_modules/projectstore/scripts/mcp.mjs +391 -0
- package/node_modules/projectstore/scripts/portable-registration.mjs +198 -0
- package/node_modules/projectstore/scripts/provenance.mjs +375 -0
- package/node_modules/projectstore/scripts/query.mjs +490 -0
- package/node_modules/projectstore/scripts/reconcile.mjs +422 -0
- package/node_modules/projectstore/scripts/statusline-launcher.mjs +141 -0
- package/node_modules/projectstore/scripts/statusline.mjs +253 -0
- package/node_modules/projectstore/scripts/story-section.mjs +209 -0
- package/node_modules/projectstore/scripts/surfaces.mjs +421 -0
- package/node_modules/projectstore/scripts/tokens.mjs +449 -0
- package/node_modules/projectstore/scripts/touch-session.mjs +336 -0
- package/node_modules/projectstore/scripts/version-guard.mjs +261 -0
- package/node_modules/projectstore/scripts/worktree.mjs +109 -0
- package/node_modules/projectstore/skills/projectstore-decision-detector/SKILL.md +40 -0
- package/node_modules/projectstore/skills/projectstore-peer-reviewer/SKILL.md +38 -0
- package/node_modules/projectstore/skills/projectstore-story-completion/SKILL.md +50 -0
- package/node_modules/projectstore/skills/projectstore-vault-communication/SKILL.md +96 -0
- package/node_modules/projectstore/templates/claude-md-block.md.tmpl +26 -0
- package/node_modules/projectstore/templates/de/adr.md.tmpl +67 -0
- package/node_modules/projectstore/templates/de/concept.md.tmpl +43 -0
- package/node_modules/projectstore/templates/de/epic.md.tmpl +59 -0
- package/node_modules/projectstore/templates/de/folder-readme.md.tmpl +14 -0
- package/node_modules/projectstore/templates/de/kanban.md.tmpl +36 -0
- package/node_modules/projectstore/templates/de/meeting.md.tmpl +38 -0
- package/node_modules/projectstore/templates/de/research.md.tmpl +47 -0
- package/node_modules/projectstore/templates/de/runbook.md.tmpl +53 -0
- package/node_modules/projectstore/templates/de/spec.md.tmpl +64 -0
- package/node_modules/projectstore/templates/de/story.md.tmpl +76 -0
- package/node_modules/projectstore/templates/de/strings.json +6 -0
- package/node_modules/projectstore/templates/en/adr.md.tmpl +67 -0
- package/node_modules/projectstore/templates/en/concept.md.tmpl +43 -0
- package/node_modules/projectstore/templates/en/epic.md.tmpl +59 -0
- package/node_modules/projectstore/templates/en/folder-readme.md.tmpl +14 -0
- package/node_modules/projectstore/templates/en/kanban.md.tmpl +36 -0
- package/node_modules/projectstore/templates/en/meeting.md.tmpl +38 -0
- package/node_modules/projectstore/templates/en/research.md.tmpl +47 -0
- package/node_modules/projectstore/templates/en/runbook.md.tmpl +53 -0
- package/node_modules/projectstore/templates/en/spec.md.tmpl +64 -0
- package/node_modules/projectstore/templates/en/story.md.tmpl +76 -0
- package/node_modules/projectstore/templates/en/strings.json +6 -0
- package/node_modules/projectstore/templates/es/adr.md.tmpl +67 -0
- package/node_modules/projectstore/templates/es/concept.md.tmpl +43 -0
- package/node_modules/projectstore/templates/es/epic.md.tmpl +59 -0
- package/node_modules/projectstore/templates/es/folder-readme.md.tmpl +14 -0
- package/node_modules/projectstore/templates/es/kanban.md.tmpl +36 -0
- package/node_modules/projectstore/templates/es/meeting.md.tmpl +38 -0
- package/node_modules/projectstore/templates/es/research.md.tmpl +47 -0
- package/node_modules/projectstore/templates/es/runbook.md.tmpl +53 -0
- package/node_modules/projectstore/templates/es/spec.md.tmpl +64 -0
- package/node_modules/projectstore/templates/es/story.md.tmpl +76 -0
- package/node_modules/projectstore/templates/es/strings.json +6 -0
- package/node_modules/projectstore/templates/fr/adr.md.tmpl +67 -0
- package/node_modules/projectstore/templates/fr/concept.md.tmpl +43 -0
- package/node_modules/projectstore/templates/fr/epic.md.tmpl +59 -0
- package/node_modules/projectstore/templates/fr/folder-readme.md.tmpl +14 -0
- package/node_modules/projectstore/templates/fr/kanban.md.tmpl +36 -0
- package/node_modules/projectstore/templates/fr/meeting.md.tmpl +38 -0
- package/node_modules/projectstore/templates/fr/research.md.tmpl +47 -0
- package/node_modules/projectstore/templates/fr/runbook.md.tmpl +53 -0
- package/node_modules/projectstore/templates/fr/spec.md.tmpl +64 -0
- package/node_modules/projectstore/templates/fr/story.md.tmpl +76 -0
- package/node_modules/projectstore/templates/fr/strings.json +6 -0
- package/node_modules/projectstore/templates/ru/adr.md.tmpl +67 -0
- package/node_modules/projectstore/templates/ru/concept.md.tmpl +43 -0
- package/node_modules/projectstore/templates/ru/epic.md.tmpl +59 -0
- package/node_modules/projectstore/templates/ru/folder-readme.md.tmpl +14 -0
- package/node_modules/projectstore/templates/ru/kanban.md.tmpl +36 -0
- package/node_modules/projectstore/templates/ru/meeting.md.tmpl +38 -0
- package/node_modules/projectstore/templates/ru/research.md.tmpl +47 -0
- package/node_modules/projectstore/templates/ru/runbook.md.tmpl +53 -0
- package/node_modules/projectstore/templates/ru/spec.md.tmpl +64 -0
- package/node_modules/projectstore/templates/ru/story.md.tmpl +76 -0
- package/node_modules/projectstore/templates/ru/strings.json +6 -0
- package/node_modules/projectstore/templates/zh/adr.md.tmpl +67 -0
- package/node_modules/projectstore/templates/zh/concept.md.tmpl +43 -0
- package/node_modules/projectstore/templates/zh/epic.md.tmpl +59 -0
- package/node_modules/projectstore/templates/zh/folder-readme.md.tmpl +14 -0
- package/node_modules/projectstore/templates/zh/kanban.md.tmpl +36 -0
- package/node_modules/projectstore/templates/zh/meeting.md.tmpl +38 -0
- package/node_modules/projectstore/templates/zh/research.md.tmpl +47 -0
- package/node_modules/projectstore/templates/zh/runbook.md.tmpl +53 -0
- package/node_modules/projectstore/templates/zh/spec.md.tmpl +64 -0
- package/node_modules/projectstore/templates/zh/story.md.tmpl +76 -0
- package/node_modules/projectstore/templates/zh/strings.json +6 -0
- package/package.json +35 -14
- package/skills/projectstore-adr/SKILL.md +76 -0
- package/skills/projectstore-agents/SKILL.md +50 -0
- package/skills/projectstore-archaeologist/SKILL.md +109 -0
- package/skills/projectstore-bind/SKILL.md +44 -0
- package/skills/projectstore-clerk/SKILL.md +126 -0
- package/skills/projectstore-codemap/SKILL.md +69 -0
- package/skills/projectstore-concept/SKILL.md +36 -0
- package/skills/projectstore-critic/SKILL.md +127 -0
- package/skills/projectstore-decision-detector/SKILL.md +59 -0
- package/skills/projectstore-doctor/SKILL.md +33 -0
- package/skills/projectstore-epic/SKILL.md +59 -0
- package/skills/projectstore-graph/SKILL.md +75 -0
- package/skills/projectstore-kanban/SKILL.md +60 -0
- package/skills/projectstore-librarian/SKILL.md +114 -0
- package/skills/projectstore-meeting/SKILL.md +36 -0
- package/skills/projectstore-peer-reviewer/SKILL.md +57 -0
- package/skills/projectstore-planner/SKILL.md +113 -0
- package/skills/projectstore-reconcile/SKILL.md +92 -0
- package/skills/projectstore-research/SKILL.md +36 -0
- package/skills/projectstore-review/SKILL.md +108 -0
- package/skills/projectstore-reviewer/SKILL.md +131 -0
- package/skills/projectstore-runbook/SKILL.md +36 -0
- package/skills/projectstore-scaffold/SKILL.md +42 -0
- package/skills/projectstore-search/SKILL.md +41 -0
- package/skills/projectstore-spec/SKILL.md +110 -0
- package/skills/projectstore-status/SKILL.md +47 -0
- package/skills/projectstore-statusline/SKILL.md +29 -0
- package/skills/projectstore-story/SKILL.md +132 -0
- package/skills/projectstore-story-completion/SKILL.md +69 -0
- package/skills/projectstore-vault-communication/SKILL.md +115 -0
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_description": "Language-independent registry of section headings, inline keywords and index-table column names recognized by the deterministic scripts (doctor, reconcile, story-section). Per id, per language: an ARRAY of accepted forms; the FIRST form of the bound language is the canonical form used when writing. Matching accepts every form of every language, so a ru-headed file lints in an en-bound vault. New sections/keywords added by any story MUST register a form for every bundled language (en, ru, es, de, fr, zh) here (PS-SPEC story-002).",
|
|
3
|
+
"headings": {
|
|
4
|
+
"description": {
|
|
5
|
+
"en": ["Description"],
|
|
6
|
+
"ru": ["Описание"],
|
|
7
|
+
"es": ["Descripción"],
|
|
8
|
+
"de": ["Beschreibung"],
|
|
9
|
+
"fr": ["Description"],
|
|
10
|
+
"zh": ["描述"]
|
|
11
|
+
},
|
|
12
|
+
"decomposition": {
|
|
13
|
+
"en": ["Decomposition"],
|
|
14
|
+
"ru": ["Декомпозиция"],
|
|
15
|
+
"es": ["Descomposición"],
|
|
16
|
+
"de": ["Zerlegung"],
|
|
17
|
+
"fr": ["Décomposition"],
|
|
18
|
+
"zh": ["任务分解"]
|
|
19
|
+
},
|
|
20
|
+
"acceptance": {
|
|
21
|
+
"en": ["Acceptance Criteria"],
|
|
22
|
+
"ru": ["Критерии приёмки", "Критерии приемки"],
|
|
23
|
+
"es": ["Criterios de aceptación"],
|
|
24
|
+
"de": ["Abnahmekriterien"],
|
|
25
|
+
"fr": ["Critères d'acceptation", "Critères d’acceptation"],
|
|
26
|
+
"zh": ["验收标准"]
|
|
27
|
+
},
|
|
28
|
+
"technical_notes": {
|
|
29
|
+
"en": ["Technical Notes"],
|
|
30
|
+
"ru": ["Технические заметки"],
|
|
31
|
+
"es": ["Notas técnicas"],
|
|
32
|
+
"de": ["Technische Hinweise"],
|
|
33
|
+
"fr": ["Notes techniques"],
|
|
34
|
+
"zh": ["技术说明"]
|
|
35
|
+
},
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"en": ["Dependencies"],
|
|
38
|
+
"ru": ["Зависимости"],
|
|
39
|
+
"es": ["Dependencias"],
|
|
40
|
+
"de": ["Abhängigkeiten"],
|
|
41
|
+
"fr": ["Dépendances"],
|
|
42
|
+
"zh": ["依赖"]
|
|
43
|
+
},
|
|
44
|
+
"attachments": {
|
|
45
|
+
"en": ["Attachments"],
|
|
46
|
+
"ru": ["Вложения"],
|
|
47
|
+
"es": ["Adjuntos"],
|
|
48
|
+
"de": ["Anhänge"],
|
|
49
|
+
"fr": ["Pièces jointes"],
|
|
50
|
+
"zh": ["附件"]
|
|
51
|
+
},
|
|
52
|
+
"implementation_plan": {
|
|
53
|
+
"en": ["Implementation Plan"],
|
|
54
|
+
"ru": ["План реализации"],
|
|
55
|
+
"es": ["Plan de implementación"],
|
|
56
|
+
"de": ["Umsetzungsplan"],
|
|
57
|
+
"fr": ["Plan d'implémentation", "Plan d’implémentation"],
|
|
58
|
+
"zh": ["实施计划"]
|
|
59
|
+
},
|
|
60
|
+
"final_summary": {
|
|
61
|
+
"en": ["Final Summary"],
|
|
62
|
+
"ru": ["Итоговое резюме"],
|
|
63
|
+
"es": ["Resumen final"],
|
|
64
|
+
"de": ["Abschlussbericht"],
|
|
65
|
+
"fr": ["Bilan final"],
|
|
66
|
+
"zh": ["最终总结"]
|
|
67
|
+
},
|
|
68
|
+
"spec_how": {
|
|
69
|
+
"en": ["How we solve"],
|
|
70
|
+
"ru": ["Как решаем"],
|
|
71
|
+
"es": ["Cómo lo resolvemos"],
|
|
72
|
+
"de": ["Lösungsweg"],
|
|
73
|
+
"fr": ["Comment on résout"],
|
|
74
|
+
"zh": ["解决方案"]
|
|
75
|
+
},
|
|
76
|
+
"spec_contracts": {
|
|
77
|
+
"en": ["Behavioral contracts"],
|
|
78
|
+
"ru": ["Контракты поведения"],
|
|
79
|
+
"es": ["Contratos de comportamiento"],
|
|
80
|
+
"de": ["Verhaltensverträge"],
|
|
81
|
+
"fr": ["Contrats de comportement"],
|
|
82
|
+
"zh": ["行为契约"]
|
|
83
|
+
},
|
|
84
|
+
"spec_acceptance": {
|
|
85
|
+
"en": ["Acceptance"],
|
|
86
|
+
"ru": ["Приёмка", "Приемка", "Приёмка / сдача"],
|
|
87
|
+
"es": ["Aceptación"],
|
|
88
|
+
"de": ["Abnahme"],
|
|
89
|
+
"fr": ["Acceptation"],
|
|
90
|
+
"zh": ["验收"]
|
|
91
|
+
},
|
|
92
|
+
"spec_modules": {
|
|
93
|
+
"en": ["Modules & files on disk"],
|
|
94
|
+
"ru": ["Модули и файлы"],
|
|
95
|
+
"es": ["Módulos y archivos"],
|
|
96
|
+
"de": ["Module und Dateien"],
|
|
97
|
+
"fr": ["Modules et fichiers"],
|
|
98
|
+
"zh": ["模块与文件"]
|
|
99
|
+
},
|
|
100
|
+
"spec_testing": {
|
|
101
|
+
"en": ["Testing"],
|
|
102
|
+
"ru": ["Тестирование"],
|
|
103
|
+
"es": ["Pruebas"],
|
|
104
|
+
"de": ["Tests"],
|
|
105
|
+
"fr": ["Tests"],
|
|
106
|
+
"zh": ["测试"]
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
"keywords": {
|
|
110
|
+
"evidence": {
|
|
111
|
+
"en": ["evidence"],
|
|
112
|
+
"ru": ["подтверждение"],
|
|
113
|
+
"es": ["evidencia"],
|
|
114
|
+
"de": ["Nachweis"],
|
|
115
|
+
"fr": ["preuve"],
|
|
116
|
+
"zh": ["证据"]
|
|
117
|
+
},
|
|
118
|
+
"stories": {
|
|
119
|
+
"en": ["stories"],
|
|
120
|
+
"ru": ["stories"],
|
|
121
|
+
"es": ["stories"],
|
|
122
|
+
"de": ["stories"],
|
|
123
|
+
"fr": ["stories"],
|
|
124
|
+
"zh": ["stories"]
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
"footers": {
|
|
128
|
+
"last_updated": {
|
|
129
|
+
"en": ["Last updated"],
|
|
130
|
+
"ru": ["Обновлено"],
|
|
131
|
+
"es": ["Última actualización"],
|
|
132
|
+
"de": ["Zuletzt aktualisiert"],
|
|
133
|
+
"fr": ["Dernière mise à jour"],
|
|
134
|
+
"zh": ["最后更新"]
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
"index_columns": {
|
|
138
|
+
"file": {
|
|
139
|
+
"en": ["File"],
|
|
140
|
+
"ru": ["Файл"],
|
|
141
|
+
"es": ["Archivo"],
|
|
142
|
+
"de": ["Datei"],
|
|
143
|
+
"fr": ["Fichier"],
|
|
144
|
+
"zh": ["文件"]
|
|
145
|
+
},
|
|
146
|
+
"title": {
|
|
147
|
+
"en": ["Title"],
|
|
148
|
+
"ru": ["Заголовок"],
|
|
149
|
+
"es": ["Título"],
|
|
150
|
+
"de": ["Titel"],
|
|
151
|
+
"fr": ["Titre"],
|
|
152
|
+
"zh": ["标题"]
|
|
153
|
+
},
|
|
154
|
+
"status": {
|
|
155
|
+
"en": ["Status"],
|
|
156
|
+
"ru": ["Статус"],
|
|
157
|
+
"es": ["Estado"],
|
|
158
|
+
"de": ["Status"],
|
|
159
|
+
"fr": ["Statut"],
|
|
160
|
+
"zh": ["状态"]
|
|
161
|
+
},
|
|
162
|
+
"date": {
|
|
163
|
+
"en": ["Date"],
|
|
164
|
+
"ru": ["Дата"],
|
|
165
|
+
"es": ["Fecha"],
|
|
166
|
+
"de": ["Datum"],
|
|
167
|
+
"fr": ["Date"],
|
|
168
|
+
"zh": ["日期"]
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "engineering",
|
|
3
|
+
"description": "Software engineering project: ADRs, epics, stories, kanban, retros, runbooks.",
|
|
4
|
+
"folders": [
|
|
5
|
+
{
|
|
6
|
+
"path": "adr",
|
|
7
|
+
"kind": "adr",
|
|
8
|
+
"readme": true,
|
|
9
|
+
"numbered": true,
|
|
10
|
+
"prefix": "ADR-",
|
|
11
|
+
"pad": 3
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"path": "specs",
|
|
15
|
+
"kind": "spec",
|
|
16
|
+
"readme": true,
|
|
17
|
+
"numbered": true,
|
|
18
|
+
"prefix": "SPEC-",
|
|
19
|
+
"pad": 3
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"path": "epics",
|
|
23
|
+
"kind": "epic",
|
|
24
|
+
"readme": true,
|
|
25
|
+
"subfolder_per_id": true,
|
|
26
|
+
"story_prefix": "story-"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"path": "research",
|
|
30
|
+
"kind": "research",
|
|
31
|
+
"readme": true
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"path": "concepts",
|
|
35
|
+
"kind": "concept",
|
|
36
|
+
"readme": true
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"path": "meetings",
|
|
40
|
+
"kind": "meeting",
|
|
41
|
+
"readme": true,
|
|
42
|
+
"date_prefix": true
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"path": "ops",
|
|
46
|
+
"kind": "runbook",
|
|
47
|
+
"readme": true
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"path": "diagrams",
|
|
51
|
+
"kind": "diagram",
|
|
52
|
+
"readme": true
|
|
53
|
+
}
|
|
54
|
+
],
|
|
55
|
+
"commands": [
|
|
56
|
+
"adr",
|
|
57
|
+
"spec",
|
|
58
|
+
"epic",
|
|
59
|
+
"story",
|
|
60
|
+
"research",
|
|
61
|
+
"concept",
|
|
62
|
+
"meeting",
|
|
63
|
+
"runbook",
|
|
64
|
+
"kanban"
|
|
65
|
+
],
|
|
66
|
+
"agents": [
|
|
67
|
+
"critic",
|
|
68
|
+
"planner",
|
|
69
|
+
"reviewer",
|
|
70
|
+
"librarian",
|
|
71
|
+
"archaeologist",
|
|
72
|
+
"clerk"
|
|
73
|
+
],
|
|
74
|
+
"kanban": {
|
|
75
|
+
"file": "kanban.md",
|
|
76
|
+
"columns": [
|
|
77
|
+
"Backlog",
|
|
78
|
+
"ToDo",
|
|
79
|
+
"In Progress",
|
|
80
|
+
"Review",
|
|
81
|
+
"Done"
|
|
82
|
+
],
|
|
83
|
+
"source": "epics/*/stories/*.md"
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
// projectstore — binding.mjs
|
|
2
|
+
//
|
|
3
|
+
// The binding of a project to a vault, as a plan and an apply: the one place
|
|
4
|
+
// that writes `<project>/<config dir>/projectstore.json`, for the CLI's `bind`
|
|
5
|
+
// and `init` verbs and, later, for the command files that today write the
|
|
6
|
+
// config by hand (the bind interview in commands/bind.md stays the in-session
|
|
7
|
+
// front-end; it will call these verbs instead of composing the JSON).
|
|
8
|
+
//
|
|
9
|
+
// The shape follows install-harness.mjs: planBind() is pure over the
|
|
10
|
+
// filesystem and returns what would be written and why, or why not;
|
|
11
|
+
// applyBind() writes only what the plan says, through writeFileAtomic. The
|
|
12
|
+
// gate is the distribution ADR's decision 6 read for a binding: naming the
|
|
13
|
+
// vault on the command line is the confirmation, so a headless `bind <vault>`
|
|
14
|
+
// proceeds; changing an existing binding is the one step that needs a second
|
|
15
|
+
// word — `--rebind` — because it silently redirects every later write to
|
|
16
|
+
// another vault. Neither asks on a terminal, unlike install: there is no
|
|
17
|
+
// preview to show — the whole write is the three values the caller just
|
|
18
|
+
// typed, and the refusal texts name the second word. A rebind keeps every
|
|
19
|
+
// other key of the config (statusline, agents, autoupdate_asked …): those are
|
|
20
|
+
// the project's decisions, not the vault's.
|
|
21
|
+
//
|
|
22
|
+
// Never reads ambient cwd or ambient env: the caller resolves the project
|
|
23
|
+
// (cli.mjs's resolveProject) and supplies the author. Normative: the CLI
|
|
24
|
+
// story's slice-2 plan (PS-CORE); the fresh config's keys mirror
|
|
25
|
+
// commands/bind.md step 3 (a test pins the two together).
|
|
26
|
+
|
|
27
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, realpathSync } from "node:fs";
|
|
28
|
+
import { join, resolve, isAbsolute, dirname } from "node:path";
|
|
29
|
+
import { homedir } from "node:os";
|
|
30
|
+
import { writeFileAtomic, pluginRoot, ensureRuntimeDir, layoutPaths } from "./lib.mjs";
|
|
31
|
+
import { configPath as harnessConfigPath } from "./harness.mjs";
|
|
32
|
+
|
|
33
|
+
export const DEFAULT_LAYOUT = "engineering";
|
|
34
|
+
export const DEFAULT_LANGUAGE = "en";
|
|
35
|
+
// commands/bind.md step 3, in its order.
|
|
36
|
+
export const FRESH_CONFIG_KEYS = Object.freeze(["vault_path", "layout", "auto_inject", "language", "tags", "default_author", "active_skills", "approval_mode"]);
|
|
37
|
+
|
|
38
|
+
export function layoutNames(root = pluginRoot()) {
|
|
39
|
+
const dir = join(root, "scaffold", "layouts");
|
|
40
|
+
if (!existsSync(dir)) return [];
|
|
41
|
+
return readdirSync(dir).filter((n) => n.endsWith(".json")).map((n) => n.replace(/\.json$/, "")).sort();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function languageNames(root = pluginRoot()) {
|
|
45
|
+
const dir = join(root, "templates");
|
|
46
|
+
if (!existsSync(dir)) return [];
|
|
47
|
+
return readdirSync(dir).filter((n) => { try { return statSync(join(dir, n)).isDirectory(); } catch { return false; } }).sort();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// `~` expanded, made absolute against the project, trailing slash dropped.
|
|
51
|
+
// `~user` is not expanded (refused by the plan), and the filesystem root is
|
|
52
|
+
// never a vault.
|
|
53
|
+
export function normaliseVaultPath(p, projectDir, home = homedir()) {
|
|
54
|
+
let s = String(p || "").trim();
|
|
55
|
+
if (!s) return null;
|
|
56
|
+
if (s === "~" || s.startsWith("~/")) s = join(home, s.slice(1));
|
|
57
|
+
else if (s.startsWith("~")) return { error: `${p}: "~user" paths are not expanded — pass the absolute path` };
|
|
58
|
+
if (!isAbsolute(s)) s = resolve(projectDir, s);
|
|
59
|
+
s = s.replace(/\/+$/, "");
|
|
60
|
+
if (!s) return { error: "the filesystem root is not a vault" };
|
|
61
|
+
return s;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const realOr = (p) => { try { return realpathSync(p); } catch { return p; } };
|
|
65
|
+
|
|
66
|
+
// The existing config as {cfg, corrupt}: a file that exists but does not parse
|
|
67
|
+
// is not "unbound" here — treating it so would overwrite the keys a rebind
|
|
68
|
+
// promises to keep.
|
|
69
|
+
function readExisting(configPath) {
|
|
70
|
+
if (!existsSync(configPath)) return { cfg: null, corrupt: false };
|
|
71
|
+
try { return { cfg: JSON.parse(readFileSync(configPath, "utf8")), corrupt: false }; } catch { return { cfg: null, corrupt: true }; }
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const shellQuote = (s) => (/^[A-Za-z0-9_\/.~-]+$/.test(s) ? s : `'${String(s).replace(/'/g, `'\\''`)}'`);
|
|
75
|
+
|
|
76
|
+
// What `bind` would write. `state`: "unbound" (fresh bind), "same" (already
|
|
77
|
+
// bound to this vault — nothing to write), "different" (bound elsewhere —
|
|
78
|
+
// needs --rebind). Refusals are the reasons apply() must not run; each has a
|
|
79
|
+
// code: USAGE (exit 2 upstream), MISSING, REBIND, BOUND, UNREADABLE,
|
|
80
|
+
// NO_PROJECT (exit 1).
|
|
81
|
+
export function planBind(projectDir, { vault, layout = null, language = null, rebind = false, init = false, author = "", env = process.env, root = pluginRoot(), home = homedir() } = {}) {
|
|
82
|
+
const refusals = [];
|
|
83
|
+
const configPath = harnessConfigPath(projectDir, env);
|
|
84
|
+
if (!existsSync(projectDir)) refusals.push({ code: "NO_PROJECT", message: `${projectDir} does not exist — a binding belongs to an existing project directory` });
|
|
85
|
+
const norm = normaliseVaultPath(vault, projectDir, home);
|
|
86
|
+
const vaultPath = typeof norm === "string" ? norm : null;
|
|
87
|
+
if (!norm) refusals.push({ code: "USAGE", message: "a vault path is required" });
|
|
88
|
+
else if (norm.error) refusals.push({ code: "USAGE", message: norm.error });
|
|
89
|
+
const layouts = layoutNames(root);
|
|
90
|
+
const languages = languageNames(root);
|
|
91
|
+
const { cfg: before, corrupt } = readExisting(configPath);
|
|
92
|
+
if (corrupt) refusals.push({ code: "UNREADABLE", message: `${configPath} exists but is not valid JSON — fix or remove it before binding; nothing is overwritten` });
|
|
93
|
+
const storedVault = before ? normaliseVaultPath(before.vault_path, projectDir, home) : null;
|
|
94
|
+
const sameVault = Boolean(vaultPath && typeof storedVault === "string" && (storedVault === vaultPath || (existsSync(storedVault) && existsSync(vaultPath) && realOr(storedVault) === realOr(vaultPath))));
|
|
95
|
+
const state = !before ? "unbound" : sameVault ? "same" : "different";
|
|
96
|
+
const chosenLayout = state === "same" ? (before.layout || DEFAULT_LAYOUT) : layout ?? (before ? before.layout : null) ?? DEFAULT_LAYOUT;
|
|
97
|
+
const chosenLanguage = state === "same" ? (before.language || DEFAULT_LANGUAGE) : language ?? (before ? before.language : null) ?? DEFAULT_LANGUAGE;
|
|
98
|
+
const ignored = state === "same" ? [layout !== null && layout !== chosenLayout ? "layout" : null, language !== null && language !== chosenLanguage ? "language" : null].filter(Boolean) : [];
|
|
99
|
+
if (state !== "same") {
|
|
100
|
+
if (layouts.length && !layouts.includes(chosenLayout)) refusals.push({ code: "USAGE", message: `unknown layout "${chosenLayout}" — one of: ${layouts.join(", ")}` });
|
|
101
|
+
if (languages.length && !languages.includes(chosenLanguage)) refusals.push({ code: "USAGE", message: `unknown language "${chosenLanguage}" — one of: ${languages.join(", ")}` });
|
|
102
|
+
}
|
|
103
|
+
const vaultExists = vaultPath ? existsSync(vaultPath) : false;
|
|
104
|
+
if (vaultPath && vaultExists && !statSync(vaultPath).isDirectory()) refusals.push({ code: "USAGE", message: `${vaultPath} is not a directory` });
|
|
105
|
+
if (vaultPath && !vaultExists && !init) refusals.push({ code: "MISSING", message: `${vaultPath} does not exist — create it first, or run \`projectstore init ${shellQuote(String(vault))}${rebind ? " --rebind" : ""}\` to create and bind in one step` });
|
|
106
|
+
if (state === "different" && !rebind) refusals.push({ code: "REBIND", message: `${projectDir} is bound to ${before.vault_path}; pass --rebind to point it at ${vaultPath} instead (every other setting is kept)` });
|
|
107
|
+
if (state === "same" && init) refusals.push({ code: "BOUND", message: `${projectDir} is already bound to ${vaultPath}` });
|
|
108
|
+
const after = state === "same" ? before : state === "different"
|
|
109
|
+
? { ...before, vault_path: vaultPath, layout: chosenLayout, language: chosenLanguage }
|
|
110
|
+
: { vault_path: vaultPath, layout: chosenLayout, auto_inject: true, language: chosenLanguage, tags: [], default_author: author || "", active_skills: true, approval_mode: "always" };
|
|
111
|
+
const keptKeys = state === "different" ? Object.keys(before).filter((k) => !["vault_path", "layout", "language"].includes(k)).sort() : [];
|
|
112
|
+
return {
|
|
113
|
+
projectDir, configPath, vault: vaultPath, vaultExists, layout: chosenLayout, language: chosenLanguage, ignored,
|
|
114
|
+
state, before, after, keptKeys, writes: refusals.length === 0 && state !== "same", createsVault: Boolean(init && vaultPath && !vaultExists && refusals.length === 0), refusals, ok: refusals.length === 0,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Writes what the plan says: the vault directory when init asked for it, the
|
|
119
|
+
// config through writeFileAtomic (its directory made first — the helper never
|
|
120
|
+
// mkdirs). Refuses to run a plan with refusals; a "same" plan writes nothing.
|
|
121
|
+
export function applyBind(plan) {
|
|
122
|
+
if (!plan.ok) throw Object.assign(new Error(plan.refusals.map((r) => r.message).join("; ")), { code: plan.refusals[0].code });
|
|
123
|
+
const done = { created_vault: false, wrote_config: false };
|
|
124
|
+
if (plan.createsVault) { mkdirSync(plan.vault, { recursive: true }); done.created_vault = true; }
|
|
125
|
+
if (plan.writes) {
|
|
126
|
+
// Contract 5 names bind among the writers of the line-merged
|
|
127
|
+
// .projectstore/.gitignore, and it was the one writer that never did:
|
|
128
|
+
// measured 2026-09-06 on a fresh project, `init` left .projectstore/
|
|
129
|
+
// holding the binding alone, so `git add -A` committed a machine-local
|
|
130
|
+
// absolute vault path. Only when we are writing the NEW path — a rebind of
|
|
131
|
+
// a not-yet-migrated project still writes the legacy file (contract 8),
|
|
132
|
+
// and creating .projectstore/ beside a legacy layout is the migration
|
|
133
|
+
// item's write, not ours.
|
|
134
|
+
if (plan.configPath === layoutPaths(plan.projectDir).binding) {
|
|
135
|
+
try { ensureRuntimeDir(plan.projectDir); } catch {}
|
|
136
|
+
}
|
|
137
|
+
mkdirSync(dirname(plan.configPath), { recursive: true });
|
|
138
|
+
writeFileAtomic(plan.configPath, JSON.stringify(plan.after, null, 2) + "\n");
|
|
139
|
+
done.wrote_config = true;
|
|
140
|
+
}
|
|
141
|
+
return done;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// The result a front-end reports: the decided values, never the file body.
|
|
145
|
+
export function bindResult(plan, done) {
|
|
146
|
+
return {
|
|
147
|
+
state: plan.state, vault_path: plan.vault, vault_exists: Boolean(done && done.created_vault) || plan.vaultExists, layout: plan.layout, language: plan.language,
|
|
148
|
+
config_path: plan.configPath, wrote: Boolean(done && done.wrote_config), created_vault: Boolean(done && done.created_vault),
|
|
149
|
+
kept_keys: plan.keptKeys, ignored: plan.ignored, refusals: plan.refusals,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export function renderBindPlan(p, done = null) {
|
|
154
|
+
const lines = [];
|
|
155
|
+
if (!p.ok) { for (const r of p.refusals) lines.push(r.message); return lines.join("\n") + "\n"; }
|
|
156
|
+
if (p.state === "same") {
|
|
157
|
+
lines.push(`Already bound to ${p.vault}${p.ignored.length ? ` — --${p.ignored.join(" and --")} ignored: a change of ${p.ignored.join("/")} is not a rebind (edit the config, or rebind to another vault)` : ""}. Run /projectstore:scaffold to (re)create the layout, or \`projectstore status\` to inspect it.`);
|
|
158
|
+
return lines.join("\n") + "\n";
|
|
159
|
+
}
|
|
160
|
+
if (done && done.created_vault) lines.push(`Created ${p.vault}`);
|
|
161
|
+
lines.push(`Wrote ${p.configPath}${p.state === "different" ? ` (rebind from ${p.before.vault_path}; kept: ${p.keptKeys.join(", ") || "nothing else"})` : ""}`);
|
|
162
|
+
lines.push(` vault_path: ${p.vault}`, ` layout: ${p.layout}`, ` language: ${p.language}`);
|
|
163
|
+
if (done) lines.push("", p.vaultExists && !done.created_vault ? "Next: `projectstore status`, or /projectstore:scaffold if the vault has no layout folders yet." : "Next: /projectstore:scaffold in a session creates the layout's folders and READMEs.");
|
|
164
|
+
return lines.join("\n") + "\n";
|
|
165
|
+
}
|