arcane-cli 0.17.1 → 0.19.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/dist/assets/.arcane/governance/git-conventions.md +9 -2
- package/dist/assets/.arcane/governance/records-conventions.md +118 -0
- package/dist/assets/.arcane/governance/universal-agent-rules.md +26 -0
- package/dist/assets/.claude/commands/spell-adopt-docs.md +9 -0
- package/dist/assets/.github/prompts/spell-adopt-docs.prompt.md +157 -0
- package/dist/assets/.github/prompts/spell-close-session.prompt.md +4 -0
- package/dist/assets/docs-baseline/gitattributes +53 -0
- package/dist/assets/docs-baseline/gitignore +15 -0
- package/dist/index.js +418 -105
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -9,7 +9,7 @@ import { fileURLToPath } from "url";
|
|
|
9
9
|
import { dirname as dirname3, join as join11 } from "path";
|
|
10
10
|
|
|
11
11
|
// src/commands/init.ts
|
|
12
|
-
import { select as select2, confirm } from "@inquirer/prompts";
|
|
12
|
+
import { select as select2, confirm, input as input2 } from "@inquirer/prompts";
|
|
13
13
|
import { join as join4 } from "path";
|
|
14
14
|
import chalk2 from "chalk";
|
|
15
15
|
|
|
@@ -98,6 +98,19 @@ var ManifestInvalidFieldError = class extends Error {
|
|
|
98
98
|
var MANIFEST_FILE = ".arcane.json";
|
|
99
99
|
var VALID_TRACKING_MODES = ["internal", "external"];
|
|
100
100
|
var VALID_EXTERNAL_PROVIDERS = ["ado", "jira", "other"];
|
|
101
|
+
var VALID_CONTENT_SENSITIVITY = ["standard", "sensitive"];
|
|
102
|
+
function isValidSubjectRoot(value) {
|
|
103
|
+
const invalid = value.trim() === "" || path2.isAbsolute(value) || // Windows drive-relative ("C:docs") and UNC-ish values also escape.
|
|
104
|
+
/^[a-zA-Z]:/.test(value) || value.startsWith("\\\\") || value.startsWith("\\") || value.split(/[\\/]/).includes("..") || // A stored subject_root is interpolated into agent prompts downstream, so
|
|
105
|
+
// keep it a single path-shaped token rather than a multi-line payload.
|
|
106
|
+
/[\r\n\0]/.test(value);
|
|
107
|
+
return !invalid;
|
|
108
|
+
}
|
|
109
|
+
function validateSubjectRoot(value, filePath) {
|
|
110
|
+
if (!isValidSubjectRoot(value)) {
|
|
111
|
+
throw new ManifestInvalidFieldError(filePath, "subject_root", value);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
101
114
|
function manifestPath(targetDir) {
|
|
102
115
|
return path2.join(targetDir, MANIFEST_FILE);
|
|
103
116
|
}
|
|
@@ -108,6 +121,19 @@ function validateTrackingFields(manifest, filePath) {
|
|
|
108
121
|
if (manifest.external_provider !== void 0 && manifest.external_provider !== null && !VALID_EXTERNAL_PROVIDERS.includes(manifest.external_provider)) {
|
|
109
122
|
throw new ManifestInvalidFieldError(filePath, "external_provider", manifest.external_provider);
|
|
110
123
|
}
|
|
124
|
+
if (manifest.content_sensitivity !== void 0 && !VALID_CONTENT_SENSITIVITY.includes(manifest.content_sensitivity)) {
|
|
125
|
+
throw new ManifestInvalidFieldError(
|
|
126
|
+
filePath,
|
|
127
|
+
"content_sensitivity",
|
|
128
|
+
manifest.content_sensitivity
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
if (manifest.subject_root !== void 0 && manifest.subject_root !== null) {
|
|
132
|
+
if (typeof manifest.subject_root !== "string") {
|
|
133
|
+
throw new ManifestInvalidFieldError(filePath, "subject_root", manifest.subject_root);
|
|
134
|
+
}
|
|
135
|
+
validateSubjectRoot(manifest.subject_root, filePath);
|
|
136
|
+
}
|
|
111
137
|
}
|
|
112
138
|
async function readManifest(targetDir) {
|
|
113
139
|
const filePath = manifestPath(targetDir);
|
|
@@ -155,15 +181,35 @@ var PROFILE_CONFIGS = [
|
|
|
155
181
|
id: "lite",
|
|
156
182
|
displayName: "Essentials",
|
|
157
183
|
description: "Spells + core governance \u2014 fast start",
|
|
158
|
-
components: [
|
|
184
|
+
components: [
|
|
185
|
+
"spells-session",
|
|
186
|
+
"spells-capture",
|
|
187
|
+
"spells-delivery",
|
|
188
|
+
"spells-review",
|
|
189
|
+
"spells-planning",
|
|
190
|
+
"spells-build",
|
|
191
|
+
"spells-venture",
|
|
192
|
+
"spells-meta",
|
|
193
|
+
"agent-output-instructions",
|
|
194
|
+
"git-conventions",
|
|
195
|
+
"testing-standards",
|
|
196
|
+
"framework-decisions",
|
|
197
|
+
"session-continuity"
|
|
198
|
+
]
|
|
159
199
|
},
|
|
160
200
|
{
|
|
161
201
|
id: "methodology",
|
|
162
202
|
displayName: "Methodology Suite",
|
|
163
203
|
description: "Spells + governance \u2014 full methodology without security/infra docs",
|
|
164
204
|
components: [
|
|
165
|
-
"
|
|
166
|
-
"
|
|
205
|
+
"spells-session",
|
|
206
|
+
"spells-capture",
|
|
207
|
+
"spells-delivery",
|
|
208
|
+
"spells-review",
|
|
209
|
+
"spells-planning",
|
|
210
|
+
"spells-build",
|
|
211
|
+
"spells-venture",
|
|
212
|
+
"spells-meta",
|
|
167
213
|
"agent-output-instructions",
|
|
168
214
|
"git-conventions",
|
|
169
215
|
"testing-standards",
|
|
@@ -175,6 +221,44 @@ var PROFILE_CONFIGS = [
|
|
|
175
221
|
"spell-authoring-standards"
|
|
176
222
|
]
|
|
177
223
|
},
|
|
224
|
+
{
|
|
225
|
+
id: "docs",
|
|
226
|
+
displayName: "Docs / Records",
|
|
227
|
+
description: "Documentation and records repositories \u2014 no code, test, or deployment workflows",
|
|
228
|
+
components: [
|
|
229
|
+
// Deliberate exclusions, per docs-mode PRD MH-01: "spells-build"
|
|
230
|
+
// (implementation, tests, stack experts, release, deployment, PRD
|
|
231
|
+
// enchantment, asset tooling), "spells-review" (adversarial CODE review
|
|
232
|
+
// -- its own workflow validates test coverage against source, which a
|
|
233
|
+
// docs repo has neither of), and "spells-venture" (hub-only).
|
|
234
|
+
//
|
|
235
|
+
// MH-01 requires every retained spell to complete its core workflow
|
|
236
|
+
// without source code, tests, CI, or an external tracker. Asserted --
|
|
237
|
+
// exclusions included, which MH-01 requires explicitly -- in
|
|
238
|
+
// test/docs-profile-registry-split.test.ts.
|
|
239
|
+
"spells-session",
|
|
240
|
+
"spells-capture",
|
|
241
|
+
"spells-delivery",
|
|
242
|
+
"spells-planning",
|
|
243
|
+
"spells-meta",
|
|
244
|
+
"spells-docs",
|
|
245
|
+
"docs-baseline",
|
|
246
|
+
"records-conventions",
|
|
247
|
+
"agent-output-instructions",
|
|
248
|
+
"git-conventions",
|
|
249
|
+
// Four retained docs spells link to agent-policies; it carries no code
|
|
250
|
+
// implication, so shipping it closes a dangling reference cheaply.
|
|
251
|
+
"agent-policies",
|
|
252
|
+
"framework-decisions",
|
|
253
|
+
"session-continuity",
|
|
254
|
+
"decision-documentation-standard",
|
|
255
|
+
"agent-work-queue-model",
|
|
256
|
+
"development-methodology",
|
|
257
|
+
"naming-conventions",
|
|
258
|
+
"universal-agent-rules",
|
|
259
|
+
"spell-authoring-standards"
|
|
260
|
+
]
|
|
261
|
+
},
|
|
178
262
|
{
|
|
179
263
|
id: "governance-only",
|
|
180
264
|
displayName: "Governance Only",
|
|
@@ -323,46 +407,159 @@ var COMPONENTS = [
|
|
|
323
407
|
description: "Copilot enforcement rules: PR link format, merge strategy, and branch naming",
|
|
324
408
|
files: [".github/instructions/agent-output.instructions.md"]
|
|
325
409
|
},
|
|
326
|
-
//
|
|
410
|
+
// Repository baseline files (EF-10 + EF-17). skipExisting: these are
|
|
411
|
+
// user-owned once written -- a repo with its own .gitattributes has an
|
|
412
|
+
// intentional policy Arcane must not overwrite. Sources are stored under a
|
|
413
|
+
// non-dotfile path and mapped via sourceOverrides; see RegistryComponent.
|
|
414
|
+
{
|
|
415
|
+
name: "docs-baseline",
|
|
416
|
+
description: "Line-ending and binary-format baseline for document repositories (.gitattributes/.gitignore)",
|
|
417
|
+
files: [".gitattributes", ".gitignore"],
|
|
418
|
+
sourceOverrides: {
|
|
419
|
+
".gitattributes": "docs-baseline/gitattributes",
|
|
420
|
+
".gitignore": "docs-baseline/gitignore"
|
|
421
|
+
},
|
|
422
|
+
skipExisting: true,
|
|
423
|
+
initOnly: true
|
|
424
|
+
},
|
|
425
|
+
{
|
|
426
|
+
name: "records-conventions",
|
|
427
|
+
description: "Supersession, tombstone, and retention conventions for records repositories",
|
|
428
|
+
files: [".arcane/governance/records-conventions.md"]
|
|
429
|
+
},
|
|
430
|
+
// ─── Spells ────────────────────────────────────────────────────────────────
|
|
431
|
+
// Split from the former monolithic `spell-prompts` + `claude-commands` pair
|
|
432
|
+
// so a profile can select spells by capability (see ARC-0NN). Grouping lives
|
|
433
|
+
// here and only here -- every spell file stays flat and unrenamed on disk.
|
|
434
|
+
//
|
|
435
|
+
// Each component carries BOTH client formats of the same spell: the Copilot
|
|
436
|
+
// prompt (.github/prompts/*.prompt.md) and the Claude Code wrapper
|
|
437
|
+
// (.claude/commands/*.md). They are never independently selectable -- every
|
|
438
|
+
// profile that wanted one always wanted the other -- and pairing them here
|
|
439
|
+
// makes it structurally impossible for the two formats of one spell to drift
|
|
440
|
+
// apart across profiles.
|
|
327
441
|
{
|
|
328
|
-
name: "
|
|
329
|
-
description: "
|
|
442
|
+
name: "spells-session",
|
|
443
|
+
description: "Session lifecycle \u2014 open, close, commit, status, version",
|
|
330
444
|
files: [
|
|
445
|
+
".github/prompts/spell-open-session.prompt.md",
|
|
446
|
+
".claude/commands/spell-open-session.md",
|
|
447
|
+
".github/prompts/spell-close-session.prompt.md",
|
|
448
|
+
".claude/commands/spell-close-session.md",
|
|
449
|
+
".github/prompts/spell-commit-work.prompt.md",
|
|
450
|
+
".claude/commands/spell-commit-work.md",
|
|
451
|
+
".github/prompts/spell-status.prompt.md",
|
|
452
|
+
".claude/commands/spell-status.md",
|
|
331
453
|
".github/prompts/spell-arcane-version.prompt.md",
|
|
332
|
-
".
|
|
454
|
+
".claude/commands/spell-arcane-version.md"
|
|
455
|
+
]
|
|
456
|
+
},
|
|
457
|
+
{
|
|
458
|
+
name: "spells-capture",
|
|
459
|
+
description: "Idea, todo, feedback and explanation capture",
|
|
460
|
+
files: [
|
|
461
|
+
".github/prompts/spell-save-idea.prompt.md",
|
|
462
|
+
".claude/commands/spell-save-idea.md",
|
|
463
|
+
".github/prompts/spell-todo.prompt.md",
|
|
464
|
+
".claude/commands/spell-todo.md",
|
|
465
|
+
".github/prompts/spell-feedback.prompt.md",
|
|
466
|
+
".claude/commands/spell-feedback.md",
|
|
467
|
+
".github/prompts/spell-suggest-feature.prompt.md",
|
|
468
|
+
".claude/commands/spell-suggest-feature.md",
|
|
469
|
+
".github/prompts/spell-document.prompt.md",
|
|
470
|
+
".claude/commands/spell-document.md",
|
|
333
471
|
".github/prompts/spell-brainstorm.prompt.md",
|
|
472
|
+
".claude/commands/spell-brainstorm.md",
|
|
473
|
+
".github/prompts/spell-explain-concept.prompt.md",
|
|
474
|
+
".claude/commands/spell-explain-concept.md"
|
|
475
|
+
]
|
|
476
|
+
},
|
|
477
|
+
{
|
|
478
|
+
name: "spells-delivery",
|
|
479
|
+
description: "Pull-request lifecycle \u2014 open a PR, respond to review feedback",
|
|
480
|
+
files: [
|
|
481
|
+
".github/prompts/spell-create-pull-request.prompt.md",
|
|
482
|
+
".claude/commands/spell-create-pull-request.md",
|
|
483
|
+
".github/prompts/spell-address-review.prompt.md",
|
|
484
|
+
".claude/commands/spell-address-review.md"
|
|
485
|
+
]
|
|
486
|
+
},
|
|
487
|
+
{
|
|
488
|
+
name: "spells-review",
|
|
489
|
+
description: "Adversarial code review \u2014 requires source and tests, so excluded from docs-only profiles",
|
|
490
|
+
files: [
|
|
491
|
+
".github/prompts/spell-review.prompt.md",
|
|
492
|
+
".claude/commands/spell-review.md",
|
|
493
|
+
".github/prompts/spell-review-batch.prompt.md",
|
|
494
|
+
".claude/commands/spell-review-batch.md"
|
|
495
|
+
]
|
|
496
|
+
},
|
|
497
|
+
{
|
|
498
|
+
name: "spells-planning",
|
|
499
|
+
description: "PRD, architecture, scoping and product review",
|
|
500
|
+
files: [
|
|
501
|
+
".github/prompts/spell-plan.prompt.md",
|
|
502
|
+
".claude/commands/spell-plan.md",
|
|
503
|
+
".github/prompts/spell-architect.prompt.md",
|
|
504
|
+
".claude/commands/spell-architect.md",
|
|
334
505
|
".github/prompts/spell-scope.prompt.md",
|
|
335
|
-
".
|
|
506
|
+
".claude/commands/spell-scope.md",
|
|
507
|
+
".github/prompts/spell-product-review.prompt.md",
|
|
508
|
+
".claude/commands/spell-product-review.md"
|
|
509
|
+
]
|
|
510
|
+
},
|
|
511
|
+
{
|
|
512
|
+
name: "spells-build",
|
|
513
|
+
description: "Code and product delivery \u2014 implementation, tests, stack experts, release, deployment, asset tooling",
|
|
514
|
+
files: [
|
|
515
|
+
".github/prompts/spell-implement.prompt.md",
|
|
516
|
+
".claude/commands/spell-implement.md",
|
|
517
|
+
".github/prompts/spell-test.prompt.md",
|
|
518
|
+
".claude/commands/spell-test.md",
|
|
519
|
+
".github/prompts/spell-full-cycle.prompt.md",
|
|
520
|
+
".claude/commands/spell-full-cycle.md",
|
|
336
521
|
".github/prompts/spell-bug.prompt.md",
|
|
522
|
+
".claude/commands/spell-bug.md",
|
|
337
523
|
".github/prompts/spell-bump.prompt.md",
|
|
338
|
-
".
|
|
339
|
-
".github/prompts/spell-close-session.prompt.md",
|
|
340
|
-
".github/prompts/spell-commit-work.prompt.md",
|
|
524
|
+
".claude/commands/spell-bump.md",
|
|
341
525
|
".github/prompts/spell-dotnet-expert.prompt.md",
|
|
342
|
-
".
|
|
343
|
-
".github/prompts/spell-explain-concept.prompt.md",
|
|
344
|
-
".github/prompts/spell-full-cycle.prompt.md",
|
|
345
|
-
".github/prompts/spell-generate-bot-icons.prompt.md",
|
|
346
|
-
".github/prompts/spell-implement.prompt.md",
|
|
347
|
-
".github/prompts/spell-open-session.prompt.md",
|
|
348
|
-
".github/prompts/spell-plan.prompt.md",
|
|
349
|
-
".github/prompts/spell-present-arcane.prompt.md",
|
|
350
|
-
".github/prompts/spell-product-review.prompt.md",
|
|
351
|
-
".github/prompts/spell-review.prompt.md",
|
|
526
|
+
".claude/commands/spell-dotnet-expert.md",
|
|
352
527
|
".github/prompts/spell-security-review.prompt.md",
|
|
528
|
+
".claude/commands/spell-security-review.md",
|
|
353
529
|
".github/prompts/spell-ship.prompt.md",
|
|
354
|
-
".
|
|
355
|
-
".github/prompts/spell-
|
|
356
|
-
".
|
|
357
|
-
|
|
358
|
-
".
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
530
|
+
".claude/commands/spell-ship.md",
|
|
531
|
+
".github/prompts/spell-enchant.prompt.md",
|
|
532
|
+
".claude/commands/spell-enchant.md",
|
|
533
|
+
".github/prompts/spell-generate-bot-icons.prompt.md",
|
|
534
|
+
".claude/commands/spell-generate-bot-icons.md"
|
|
535
|
+
]
|
|
536
|
+
},
|
|
537
|
+
{
|
|
538
|
+
name: "spells-docs",
|
|
539
|
+
description: "Documentation and records workflows \u2014 adopt an existing document tree",
|
|
540
|
+
files: [
|
|
541
|
+
".github/prompts/spell-adopt-docs.prompt.md",
|
|
542
|
+
".claude/commands/spell-adopt-docs.md"
|
|
543
|
+
]
|
|
544
|
+
},
|
|
545
|
+
{
|
|
546
|
+
name: "spells-venture",
|
|
547
|
+
description: "Hub-only venture management",
|
|
548
|
+
files: [
|
|
549
|
+
".github/prompts/spell-summon-venture.prompt.md",
|
|
550
|
+
".claude/commands/spell-summon-venture.md",
|
|
362
551
|
".github/prompts/spell-manifest.prompt.md",
|
|
363
|
-
".
|
|
364
|
-
|
|
365
|
-
|
|
552
|
+
".claude/commands/spell-manifest.md"
|
|
553
|
+
]
|
|
554
|
+
},
|
|
555
|
+
{
|
|
556
|
+
name: "spells-meta",
|
|
557
|
+
description: "Arcane-about-Arcane \u2014 presentation and documentation drift detection",
|
|
558
|
+
files: [
|
|
559
|
+
".github/prompts/spell-present-arcane.prompt.md",
|
|
560
|
+
".claude/commands/spell-present-arcane.md",
|
|
561
|
+
".github/prompts/spell-check-drift.prompt.md",
|
|
562
|
+
".claude/commands/spell-check-drift.md"
|
|
366
563
|
]
|
|
367
564
|
},
|
|
368
565
|
// Templates
|
|
@@ -396,53 +593,11 @@ var COMPONENTS = [
|
|
|
396
593
|
name: "agent-definitions",
|
|
397
594
|
description: "Agent policy reference template (for governance-only setups \u2014 use `spell agents init` for the full agent system)",
|
|
398
595
|
files: [".arcane/agents/agent-policies-template.md"]
|
|
399
|
-
}
|
|
596
|
+
}
|
|
400
597
|
// NOTE: the static `agent-files` component was retired — `.github/agents/*.agent.md`
|
|
401
598
|
// files are generated per-roster by `spell agents init` / `spell agents sync`, so
|
|
402
599
|
// installing a fixed-name set via `spell init` shipped stale names and collided
|
|
403
600
|
// with the generated output.
|
|
404
|
-
// Claude commands
|
|
405
|
-
{
|
|
406
|
-
name: "claude-commands",
|
|
407
|
-
description: "Claude Code slash command wrappers for all 34 Arcane spells",
|
|
408
|
-
files: [
|
|
409
|
-
".claude/commands/spell-arcane-version.md",
|
|
410
|
-
".claude/commands/spell-architect.md",
|
|
411
|
-
".claude/commands/spell-brainstorm.md",
|
|
412
|
-
".claude/commands/spell-scope.md",
|
|
413
|
-
".claude/commands/spell-summon-venture.md",
|
|
414
|
-
".claude/commands/spell-bug.md",
|
|
415
|
-
".claude/commands/spell-bump.md",
|
|
416
|
-
".claude/commands/spell-check-drift.md",
|
|
417
|
-
".claude/commands/spell-close-session.md",
|
|
418
|
-
".claude/commands/spell-commit-work.md",
|
|
419
|
-
".claude/commands/spell-dotnet-expert.md",
|
|
420
|
-
".claude/commands/spell-enchant.md",
|
|
421
|
-
".claude/commands/spell-explain-concept.md",
|
|
422
|
-
".claude/commands/spell-full-cycle.md",
|
|
423
|
-
".claude/commands/spell-generate-bot-icons.md",
|
|
424
|
-
".claude/commands/spell-implement.md",
|
|
425
|
-
".claude/commands/spell-open-session.md",
|
|
426
|
-
".claude/commands/spell-plan.md",
|
|
427
|
-
".claude/commands/spell-present-arcane.md",
|
|
428
|
-
".claude/commands/spell-product-review.md",
|
|
429
|
-
".claude/commands/spell-review.md",
|
|
430
|
-
".claude/commands/spell-security-review.md",
|
|
431
|
-
".claude/commands/spell-ship.md",
|
|
432
|
-
".claude/commands/spell-suggest-feature.md",
|
|
433
|
-
".claude/commands/spell-test.md",
|
|
434
|
-
".claude/commands/spell-todo.md",
|
|
435
|
-
// Workflow spell additions — PR lifecycle, status, capture, feedback
|
|
436
|
-
".claude/commands/spell-address-review.md",
|
|
437
|
-
".claude/commands/spell-create-pull-request.md",
|
|
438
|
-
".claude/commands/spell-document.md",
|
|
439
|
-
".claude/commands/spell-feedback.md",
|
|
440
|
-
".claude/commands/spell-manifest.md",
|
|
441
|
-
".claude/commands/spell-save-idea.md",
|
|
442
|
-
".claude/commands/spell-status.md",
|
|
443
|
-
".claude/commands/spell-review-batch.md"
|
|
444
|
-
]
|
|
445
|
-
}
|
|
446
601
|
];
|
|
447
602
|
function buildProfileMap() {
|
|
448
603
|
const allNames = COMPONENTS.map((c) => c.name);
|
|
@@ -454,6 +609,23 @@ function buildProfileMap() {
|
|
|
454
609
|
}
|
|
455
610
|
var PROFILE_MAP = buildProfileMap();
|
|
456
611
|
var componentIndex = new Map(COMPONENTS.map((c) => [c.name, c]));
|
|
612
|
+
var SPELL_COMPONENT_NAMES = COMPONENTS.map((c) => c.name).filter(
|
|
613
|
+
(n) => n.startsWith("spells-")
|
|
614
|
+
);
|
|
615
|
+
var LEGACY_MONOLITH_REPLACEMENTS = [
|
|
616
|
+
"spells-session",
|
|
617
|
+
"spells-capture",
|
|
618
|
+
"spells-delivery",
|
|
619
|
+
"spells-review",
|
|
620
|
+
"spells-planning",
|
|
621
|
+
"spells-build",
|
|
622
|
+
"spells-venture",
|
|
623
|
+
"spells-meta"
|
|
624
|
+
];
|
|
625
|
+
var LEGACY_COMPONENT_MIGRATIONS = Object.assign(/* @__PURE__ */ Object.create(null), {
|
|
626
|
+
"spell-prompts": LEGACY_MONOLITH_REPLACEMENTS,
|
|
627
|
+
"claude-commands": LEGACY_MONOLITH_REPLACEMENTS
|
|
628
|
+
});
|
|
457
629
|
function getComponent(name) {
|
|
458
630
|
const component = componentIndex.get(name);
|
|
459
631
|
if (!component) {
|
|
@@ -1837,27 +2009,28 @@ async function ensureLocalPullRebase(cwd) {
|
|
|
1837
2009
|
}
|
|
1838
2010
|
|
|
1839
2011
|
// src/commands/init.ts
|
|
1840
|
-
var VALID_PROFILES = ["full", "lite", "governance-only", "methodology"];
|
|
2012
|
+
var VALID_PROFILES = ["full", "lite", "governance-only", "methodology", "docs"];
|
|
2013
|
+
var PROFILE_ICONS = {
|
|
2014
|
+
full: "\u{1F9D9}",
|
|
2015
|
+
lite: "\u26A1",
|
|
2016
|
+
docs: "\u{1F4D6}"
|
|
2017
|
+
};
|
|
2018
|
+
function isSpellComponent(name) {
|
|
2019
|
+
return name.startsWith("spells-");
|
|
2020
|
+
}
|
|
1841
2021
|
function categorizeComponents(components) {
|
|
1842
2022
|
const groups = [];
|
|
1843
|
-
const
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
label: "Copilot Spells",
|
|
1848
|
-
count: prompts.files.length
|
|
1849
|
-
});
|
|
2023
|
+
const spellFiles = components.filter((c) => isSpellComponent(c.name)).flatMap((c) => c.files);
|
|
2024
|
+
const promptCount = spellFiles.filter((f) => f.startsWith(".github/prompts/")).length;
|
|
2025
|
+
if (promptCount > 0) {
|
|
2026
|
+
groups.push({ icon: "\u2728", label: "Copilot Spells", count: promptCount });
|
|
1850
2027
|
}
|
|
1851
|
-
const
|
|
1852
|
-
if (
|
|
1853
|
-
groups.push({
|
|
1854
|
-
icon: "\u26A1",
|
|
1855
|
-
label: "Claude Spells",
|
|
1856
|
-
count: claude.files.length
|
|
1857
|
-
});
|
|
2028
|
+
const claudeCount = spellFiles.filter((f) => f.startsWith(".claude/commands/")).length;
|
|
2029
|
+
if (claudeCount > 0) {
|
|
2030
|
+
groups.push({ icon: "\u26A1", label: "Claude Spells", count: claudeCount });
|
|
1858
2031
|
}
|
|
1859
2032
|
const governance = components.filter(
|
|
1860
|
-
(c) => c.name
|
|
2033
|
+
(c) => !isSpellComponent(c.name) && c.name !== "venture-template" && c.name !== "agent-definitions" && c.files.some((f) => f.startsWith(".arcane/governance/"))
|
|
1861
2034
|
);
|
|
1862
2035
|
if (governance.length > 0) {
|
|
1863
2036
|
groups.push({
|
|
@@ -1963,7 +2136,7 @@ async function runInit(options, targetDir, assetsDir, packageVersion) {
|
|
|
1963
2136
|
const profileComponents = getProfile(p.id);
|
|
1964
2137
|
return {
|
|
1965
2138
|
value: p.id,
|
|
1966
|
-
name: `${p.id
|
|
2139
|
+
name: `${PROFILE_ICONS[p.id] ?? "\u{1F4CB}"} ${p.displayName} \u2014 ${p.description}`,
|
|
1967
2140
|
description: buildProfileDescription(profileComponents, p.id)
|
|
1968
2141
|
};
|
|
1969
2142
|
})
|
|
@@ -1997,7 +2170,7 @@ async function runInit(options, targetDir, assetsDir, packageVersion) {
|
|
|
1997
2170
|
for (const component of components) {
|
|
1998
2171
|
const installedFiles = [];
|
|
1999
2172
|
for (const file of component.files) {
|
|
2000
|
-
const srcPath = join4(assetsDir, file);
|
|
2173
|
+
const srcPath = join4(assetsDir, component.sourceOverrides?.[file] ?? file);
|
|
2001
2174
|
if (options.dryRun) {
|
|
2002
2175
|
printDryRun(`Would copy: ${file}`);
|
|
2003
2176
|
} else {
|
|
@@ -2062,7 +2235,7 @@ async function runInit(options, targetDir, assetsDir, packageVersion) {
|
|
|
2062
2235
|
}
|
|
2063
2236
|
let tracking_mode;
|
|
2064
2237
|
let external_provider;
|
|
2065
|
-
if (profile === "governance-only" || profile === "methodology") {
|
|
2238
|
+
if (profile === "governance-only" || profile === "methodology" || profile === "docs") {
|
|
2066
2239
|
tracking_mode = "internal";
|
|
2067
2240
|
external_provider = null;
|
|
2068
2241
|
} else if (!options.profile) {
|
|
@@ -2087,13 +2260,57 @@ async function runInit(options, targetDir, assetsDir, packageVersion) {
|
|
|
2087
2260
|
external_provider = null;
|
|
2088
2261
|
}
|
|
2089
2262
|
}
|
|
2263
|
+
let subject_root;
|
|
2264
|
+
if (profile === "docs" && !options.profile) {
|
|
2265
|
+
console.log();
|
|
2266
|
+
const shape = await select2({
|
|
2267
|
+
message: "What does this repository hold?",
|
|
2268
|
+
choices: [
|
|
2269
|
+
{
|
|
2270
|
+
value: "root",
|
|
2271
|
+
name: "One subject, at the repository root (documents sit alongside Arcane's files)"
|
|
2272
|
+
},
|
|
2273
|
+
{ value: "subdir", name: "One subject, in its own directory" },
|
|
2274
|
+
{ value: "portfolio", name: "Several subjects or ventures (decide per-document later)" }
|
|
2275
|
+
]
|
|
2276
|
+
});
|
|
2277
|
+
if (shape === "root") {
|
|
2278
|
+
subject_root = ".";
|
|
2279
|
+
} else if (shape === "subdir") {
|
|
2280
|
+
const answer = await input2({
|
|
2281
|
+
message: "Directory holding the subject's documents:",
|
|
2282
|
+
default: "docs",
|
|
2283
|
+
validate: (v) => isValidSubjectRoot(v.trim() || "docs") || "Must be a relative path inside the repository (no leading /, drive letter, or ..)."
|
|
2284
|
+
});
|
|
2285
|
+
subject_root = answer.trim() || "docs";
|
|
2286
|
+
} else if (shape === "portfolio") {
|
|
2287
|
+
subject_root = null;
|
|
2288
|
+
}
|
|
2289
|
+
}
|
|
2290
|
+
let content_sensitivity;
|
|
2291
|
+
if (!options.profile) {
|
|
2292
|
+
console.log();
|
|
2293
|
+
content_sensitivity = await select2({
|
|
2294
|
+
message: "How should agents treat this repository's contents?",
|
|
2295
|
+
choices: [
|
|
2296
|
+
{ value: "standard", name: "Standard \u2014 agents may quote contents in journals and decisions" },
|
|
2297
|
+
{
|
|
2298
|
+
value: "sensitive",
|
|
2299
|
+
name: "Sensitive \u2014 agents reference documents by path, never transcribe them"
|
|
2300
|
+
}
|
|
2301
|
+
],
|
|
2302
|
+
default: "standard"
|
|
2303
|
+
});
|
|
2304
|
+
}
|
|
2090
2305
|
const manifest = {
|
|
2091
2306
|
version: packageVersion,
|
|
2092
2307
|
profile,
|
|
2093
2308
|
installedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2094
2309
|
components: installedComponents,
|
|
2095
2310
|
...role ? { role } : {},
|
|
2096
|
-
...tracking_mode ? { tracking_mode, external_provider } : {}
|
|
2311
|
+
...tracking_mode ? { tracking_mode, external_provider } : {},
|
|
2312
|
+
...subject_root !== void 0 ? { subject_root } : {},
|
|
2313
|
+
...content_sensitivity ? { content_sensitivity } : {}
|
|
2097
2314
|
};
|
|
2098
2315
|
await writeManifest(targetDir, manifest);
|
|
2099
2316
|
printSuccess(`Initialized with profile "${profile}" \u2014 ${fileCount} files installed`);
|
|
@@ -2177,7 +2394,7 @@ async function runAdd(name, options, targetDir, assetsDir, packageVersion) {
|
|
|
2177
2394
|
let fileCount = 0;
|
|
2178
2395
|
const installedFiles = [];
|
|
2179
2396
|
for (const file of component.files) {
|
|
2180
|
-
const srcPath = join5(assetsDir, file);
|
|
2397
|
+
const srcPath = join5(assetsDir, component.sourceOverrides?.[file] ?? file);
|
|
2181
2398
|
if (options.dryRun) {
|
|
2182
2399
|
console.log(` [dry-run] Would copy: ${file}`);
|
|
2183
2400
|
} else {
|
|
@@ -2220,7 +2437,7 @@ async function runAdd(name, options, targetDir, assetsDir, packageVersion) {
|
|
|
2220
2437
|
import { join as join7 } from "path";
|
|
2221
2438
|
|
|
2222
2439
|
// src/modules/hub.ts
|
|
2223
|
-
import { confirm as confirm2, select as select3 } from "@inquirer/prompts";
|
|
2440
|
+
import { confirm as confirm2, select as select3, input as input3 } from "@inquirer/prompts";
|
|
2224
2441
|
import { readdir as readdir3, writeFile as writeFile5 } from "fs/promises";
|
|
2225
2442
|
import { join as join6 } from "path";
|
|
2226
2443
|
var MANIFEST_RETROFITS = [
|
|
@@ -2241,7 +2458,7 @@ var MANIFEST_RETROFITS = [
|
|
|
2241
2458
|
// Mirrors init.ts's Step 5b branching exactly (EF-14 D5): docs-only
|
|
2242
2459
|
// profiles get a silent default, full/lite get asked.
|
|
2243
2460
|
ask: async (manifest) => {
|
|
2244
|
-
if (manifest.profile === "governance-only" || manifest.profile === "methodology") {
|
|
2461
|
+
if (manifest.profile === "governance-only" || manifest.profile === "methodology" || manifest.profile === "docs") {
|
|
2245
2462
|
return { tracking_mode: "internal", external_provider: null };
|
|
2246
2463
|
}
|
|
2247
2464
|
const tracking_mode = await select3({
|
|
@@ -2264,6 +2481,64 @@ var MANIFEST_RETROFITS = [
|
|
|
2264
2481
|
}
|
|
2265
2482
|
return { tracking_mode, external_provider: null };
|
|
2266
2483
|
}
|
|
2484
|
+
},
|
|
2485
|
+
{
|
|
2486
|
+
field: "content_sensitivity",
|
|
2487
|
+
needsRetrofit: (m) => m.content_sensitivity === void 0,
|
|
2488
|
+
// EF-12. Asked for every profile -- a code repo can hold sensitive records
|
|
2489
|
+
// too -- and defaults to "standard", so an operator who just presses enter
|
|
2490
|
+
// keeps today's behaviour exactly.
|
|
2491
|
+
ask: async () => {
|
|
2492
|
+
const content_sensitivity = await select3({
|
|
2493
|
+
message: "How should agents treat this repository's contents?",
|
|
2494
|
+
choices: [
|
|
2495
|
+
{
|
|
2496
|
+
value: "standard",
|
|
2497
|
+
name: "Standard \u2014 agents may quote contents in journals and decisions"
|
|
2498
|
+
},
|
|
2499
|
+
{
|
|
2500
|
+
value: "sensitive",
|
|
2501
|
+
name: "Sensitive \u2014 agents reference documents by path, never transcribe them"
|
|
2502
|
+
}
|
|
2503
|
+
],
|
|
2504
|
+
default: "standard"
|
|
2505
|
+
});
|
|
2506
|
+
return { content_sensitivity };
|
|
2507
|
+
}
|
|
2508
|
+
},
|
|
2509
|
+
{
|
|
2510
|
+
field: "subject_root",
|
|
2511
|
+
// EF-07. Only the docs profile is asked: other profiles describe code or a
|
|
2512
|
+
// venture portfolio, where "what is this repo about" is already answered.
|
|
2513
|
+
// A docs install that legitimately holds several subjects answers
|
|
2514
|
+
// "portfolio" and stays unset -- so this retrofit deliberately does NOT
|
|
2515
|
+
// re-ask on every update. It is gated on the field being absent AND the
|
|
2516
|
+
// profile being docs, and once answered (either way) it never fires again,
|
|
2517
|
+
// because "portfolio" writes an explicit empty marker.
|
|
2518
|
+
needsRetrofit: (m) => m.profile === "docs" && m.subject_root === void 0,
|
|
2519
|
+
ask: async () => {
|
|
2520
|
+
const shape = await select3({
|
|
2521
|
+
message: "What does this repository hold?",
|
|
2522
|
+
choices: [
|
|
2523
|
+
{
|
|
2524
|
+
value: "root",
|
|
2525
|
+
name: "One subject, at the repository root (documents sit alongside Arcane's files)"
|
|
2526
|
+
},
|
|
2527
|
+
{ value: "subdir", name: "One subject, in its own directory" },
|
|
2528
|
+
{ value: "portfolio", name: "Several subjects or ventures" }
|
|
2529
|
+
]
|
|
2530
|
+
});
|
|
2531
|
+
if (shape === "root") return { subject_root: "." };
|
|
2532
|
+
if (shape === "subdir") {
|
|
2533
|
+
const answer = await input3({
|
|
2534
|
+
message: "Directory holding the subject's documents:",
|
|
2535
|
+
default: "docs",
|
|
2536
|
+
validate: (v) => isValidSubjectRoot(v.trim() || "docs") || "Must be a relative path inside the repository (no leading /, drive letter, or ..)."
|
|
2537
|
+
});
|
|
2538
|
+
return { subject_root: answer.trim() || "docs" };
|
|
2539
|
+
}
|
|
2540
|
+
return { subject_root: null };
|
|
2541
|
+
}
|
|
2267
2542
|
}
|
|
2268
2543
|
];
|
|
2269
2544
|
async function runManifestRetrofits(manifest) {
|
|
@@ -2328,6 +2603,25 @@ async function offerRegistryScaffold(targetDir, businessRoot) {
|
|
|
2328
2603
|
}
|
|
2329
2604
|
|
|
2330
2605
|
// src/commands/update.ts
|
|
2606
|
+
function migrateLegacyComponents(components) {
|
|
2607
|
+
const out = [];
|
|
2608
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2609
|
+
for (const entry of components) {
|
|
2610
|
+
const replacements = LEGACY_COMPONENT_MIGRATIONS[entry.name];
|
|
2611
|
+
if (!replacements) {
|
|
2612
|
+
if (seen.has(entry.name)) continue;
|
|
2613
|
+
seen.add(entry.name);
|
|
2614
|
+
out.push(entry);
|
|
2615
|
+
continue;
|
|
2616
|
+
}
|
|
2617
|
+
for (const name of replacements) {
|
|
2618
|
+
if (seen.has(name)) continue;
|
|
2619
|
+
seen.add(name);
|
|
2620
|
+
out.push({ name, files: [], installedVersion: entry.installedVersion });
|
|
2621
|
+
}
|
|
2622
|
+
}
|
|
2623
|
+
return out;
|
|
2624
|
+
}
|
|
2331
2625
|
async function runUpdate(options, targetDir, assetsDir, packageVersion) {
|
|
2332
2626
|
let manifest;
|
|
2333
2627
|
try {
|
|
@@ -2380,7 +2674,8 @@ async function runUpdate(options, targetDir, assetsDir, packageVersion) {
|
|
|
2380
2674
|
}
|
|
2381
2675
|
let fileCount = 0;
|
|
2382
2676
|
const updatedComponents = [];
|
|
2383
|
-
|
|
2677
|
+
const componentsToUpdate = migrateLegacyComponents(manifest.components);
|
|
2678
|
+
for (const installed of componentsToUpdate) {
|
|
2384
2679
|
let component;
|
|
2385
2680
|
try {
|
|
2386
2681
|
component = getComponent(installed.name);
|
|
@@ -2394,8 +2689,15 @@ async function runUpdate(options, targetDir, assetsDir, packageVersion) {
|
|
|
2394
2689
|
}
|
|
2395
2690
|
const updatedFiles = [];
|
|
2396
2691
|
for (const file of component.files) {
|
|
2397
|
-
const srcPath = join7(assetsDir, file);
|
|
2398
|
-
const
|
|
2692
|
+
const srcPath = join7(assetsDir, component.sourceOverrides?.[file] ?? file);
|
|
2693
|
+
const targetExists = await fileExists(join7(targetDir, file));
|
|
2694
|
+
const preserveExisting = Boolean(component.skipExisting) && targetExists;
|
|
2695
|
+
if (component.initOnly && !targetExists) {
|
|
2696
|
+
console.log(
|
|
2697
|
+
` ! Missing: ${file} \u2014 not added automatically, because doing so would change how Git treats existing files. Run "spell add ${component.name}" if you want it.`
|
|
2698
|
+
);
|
|
2699
|
+
continue;
|
|
2700
|
+
}
|
|
2399
2701
|
if (preserveExisting) {
|
|
2400
2702
|
console.log(` ${options.dryRun ? "[dry-run] Would preserve" : "Preserved"}: ${file}`);
|
|
2401
2703
|
} else if (options.dryRun) {
|
|
@@ -2405,7 +2707,9 @@ async function runUpdate(options, targetDir, assetsDir, packageVersion) {
|
|
|
2405
2707
|
await copyFile(srcPath, targetDir, file, { force: true });
|
|
2406
2708
|
fileCount++;
|
|
2407
2709
|
}
|
|
2408
|
-
|
|
2710
|
+
if (!preserveExisting || installed.files.includes(file)) {
|
|
2711
|
+
updatedFiles.push(file);
|
|
2712
|
+
}
|
|
2409
2713
|
}
|
|
2410
2714
|
for (const dir of component.directories ?? []) {
|
|
2411
2715
|
const srcDirPath = join7(assetsDir, dir);
|
|
@@ -2436,7 +2740,16 @@ async function runUpdate(options, targetDir, assetsDir, packageVersion) {
|
|
|
2436
2740
|
}
|
|
2437
2741
|
return;
|
|
2438
2742
|
}
|
|
2439
|
-
const
|
|
2743
|
+
const interactive = Boolean(process.stdin.isTTY);
|
|
2744
|
+
const retrofitPatch = interactive ? await runManifestRetrofits(manifest) : {};
|
|
2745
|
+
if (!interactive) {
|
|
2746
|
+
const pending = MANIFEST_RETROFITS.filter((r) => r.needsRetrofit(manifest));
|
|
2747
|
+
if (pending.length > 0) {
|
|
2748
|
+
console.log(
|
|
2749
|
+
` ! Skipped ${pending.length} manifest question${pending.length === 1 ? "" : "s"} (${pending.map((r) => r.field).join(", ")}) \u2014 no interactive terminal. Run \`spell update\` from a terminal to answer.`
|
|
2750
|
+
);
|
|
2751
|
+
}
|
|
2752
|
+
}
|
|
2440
2753
|
const updated = {
|
|
2441
2754
|
...manifest,
|
|
2442
2755
|
version: packageVersion,
|
|
@@ -2984,7 +3297,7 @@ program.name("spell").description("Arcane framework CLI \u2014 scaffold and mana
|
|
|
2984
3297
|
await printWelcome(pkg.version, process.cwd());
|
|
2985
3298
|
program.outputHelp();
|
|
2986
3299
|
});
|
|
2987
|
-
program.command("init").description("Scaffold Arcane framework files into the current repository").option("--profile <profile>", "Profile: full | lite | methodology | governance-only").option("--force", "Overwrite existing files without error").option("--dry-run", "Preview what would be installed without making changes").action(
|
|
3300
|
+
program.command("init").description("Scaffold Arcane framework files into the current repository").option("--profile <profile>", "Profile: full | lite | methodology | docs | governance-only").option("--force", "Overwrite existing files without error").option("--dry-run", "Preview what would be installed without making changes").action(
|
|
2988
3301
|
async (opts) => {
|
|
2989
3302
|
await runInit(
|
|
2990
3303
|
{
|