arcane-cli 0.17.0 → 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 +441 -107
- 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) {
|
|
@@ -1662,6 +1834,8 @@ var READ_SUBCOMMANDS = /* @__PURE__ */ new Set([
|
|
|
1662
1834
|
"ls-files",
|
|
1663
1835
|
"ls-tree",
|
|
1664
1836
|
"symbolic-ref",
|
|
1837
|
+
"show-ref",
|
|
1838
|
+
"for-each-ref",
|
|
1665
1839
|
"merge-base",
|
|
1666
1840
|
"blame",
|
|
1667
1841
|
"shortlog",
|
|
@@ -1788,10 +1962,25 @@ async function correctUnbornMasterDefault(cwd) {
|
|
|
1788
1962
|
return { corrected: false, to: "main" };
|
|
1789
1963
|
} catch {
|
|
1790
1964
|
}
|
|
1965
|
+
let mainRef;
|
|
1791
1966
|
try {
|
|
1792
|
-
await runGit(cwd, ["
|
|
1793
|
-
return { corrected: false, to: "main" };
|
|
1967
|
+
mainRef = await runGit(cwd, ["for-each-ref", "--format=%(objectname)", "refs/heads/main"]);
|
|
1794
1968
|
} catch {
|
|
1969
|
+
return { corrected: false, to: "main", blockedReason: "target-unreadable" };
|
|
1970
|
+
}
|
|
1971
|
+
if (mainRef.stdout.trim() !== "") {
|
|
1972
|
+
return { corrected: false, to: "main" };
|
|
1973
|
+
}
|
|
1974
|
+
if (mainRef.stderr.trim() !== "") {
|
|
1975
|
+
return { corrected: false, to: "main", blockedReason: "target-unreadable" };
|
|
1976
|
+
}
|
|
1977
|
+
try {
|
|
1978
|
+
await runGit(cwd, ["symbolic-ref", "-q", "refs/heads/main"]);
|
|
1979
|
+
return { corrected: false, to: "main", blockedReason: "target-unreadable" };
|
|
1980
|
+
} catch (error) {
|
|
1981
|
+
if (error.code !== 1) {
|
|
1982
|
+
return { corrected: false, to: "main", blockedReason: "target-unreadable" };
|
|
1983
|
+
}
|
|
1795
1984
|
}
|
|
1796
1985
|
await runGit(cwd, ["symbolic-ref", "HEAD", "refs/heads/main"]);
|
|
1797
1986
|
return { corrected: true, from: current, to: "main" };
|
|
@@ -1820,27 +2009,28 @@ async function ensureLocalPullRebase(cwd) {
|
|
|
1820
2009
|
}
|
|
1821
2010
|
|
|
1822
2011
|
// src/commands/init.ts
|
|
1823
|
-
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
|
+
}
|
|
1824
2021
|
function categorizeComponents(components) {
|
|
1825
2022
|
const groups = [];
|
|
1826
|
-
const
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
label: "Copilot Spells",
|
|
1831
|
-
count: prompts.files.length
|
|
1832
|
-
});
|
|
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 });
|
|
1833
2027
|
}
|
|
1834
|
-
const
|
|
1835
|
-
if (
|
|
1836
|
-
groups.push({
|
|
1837
|
-
icon: "\u26A1",
|
|
1838
|
-
label: "Claude Spells",
|
|
1839
|
-
count: claude.files.length
|
|
1840
|
-
});
|
|
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 });
|
|
1841
2031
|
}
|
|
1842
2032
|
const governance = components.filter(
|
|
1843
|
-
(c) => c.name
|
|
2033
|
+
(c) => !isSpellComponent(c.name) && c.name !== "venture-template" && c.name !== "agent-definitions" && c.files.some((f) => f.startsWith(".arcane/governance/"))
|
|
1844
2034
|
);
|
|
1845
2035
|
if (governance.length > 0) {
|
|
1846
2036
|
groups.push({
|
|
@@ -1907,6 +2097,10 @@ async function runInit(options, targetDir, assetsDir, packageVersion) {
|
|
|
1907
2097
|
printInfo(
|
|
1908
2098
|
`Repointed the unborn branch from "${correction.from}" to "${correction.to}" (avoids the Git for Windows default-branch trap).`
|
|
1909
2099
|
);
|
|
2100
|
+
} else if (correction.blockedReason === "target-unreadable") {
|
|
2101
|
+
printWarning(
|
|
2102
|
+
'This repo\'s "main" branch ref exists but could not be read (broken ref). Left the current branch alone \u2014 repointing onto an unreadable ref risks attaching your staged work to history Arcane cannot see. Check `git for-each-ref refs/heads/main` before committing.'
|
|
2103
|
+
);
|
|
1910
2104
|
}
|
|
1911
2105
|
}
|
|
1912
2106
|
if (gitState.status === "ready" && gitState.uncommittedChanges > 0) {
|
|
@@ -1942,7 +2136,7 @@ async function runInit(options, targetDir, assetsDir, packageVersion) {
|
|
|
1942
2136
|
const profileComponents = getProfile(p.id);
|
|
1943
2137
|
return {
|
|
1944
2138
|
value: p.id,
|
|
1945
|
-
name: `${p.id
|
|
2139
|
+
name: `${PROFILE_ICONS[p.id] ?? "\u{1F4CB}"} ${p.displayName} \u2014 ${p.description}`,
|
|
1946
2140
|
description: buildProfileDescription(profileComponents, p.id)
|
|
1947
2141
|
};
|
|
1948
2142
|
})
|
|
@@ -1976,7 +2170,7 @@ async function runInit(options, targetDir, assetsDir, packageVersion) {
|
|
|
1976
2170
|
for (const component of components) {
|
|
1977
2171
|
const installedFiles = [];
|
|
1978
2172
|
for (const file of component.files) {
|
|
1979
|
-
const srcPath = join4(assetsDir, file);
|
|
2173
|
+
const srcPath = join4(assetsDir, component.sourceOverrides?.[file] ?? file);
|
|
1980
2174
|
if (options.dryRun) {
|
|
1981
2175
|
printDryRun(`Would copy: ${file}`);
|
|
1982
2176
|
} else {
|
|
@@ -2041,7 +2235,7 @@ async function runInit(options, targetDir, assetsDir, packageVersion) {
|
|
|
2041
2235
|
}
|
|
2042
2236
|
let tracking_mode;
|
|
2043
2237
|
let external_provider;
|
|
2044
|
-
if (profile === "governance-only" || profile === "methodology") {
|
|
2238
|
+
if (profile === "governance-only" || profile === "methodology" || profile === "docs") {
|
|
2045
2239
|
tracking_mode = "internal";
|
|
2046
2240
|
external_provider = null;
|
|
2047
2241
|
} else if (!options.profile) {
|
|
@@ -2066,13 +2260,57 @@ async function runInit(options, targetDir, assetsDir, packageVersion) {
|
|
|
2066
2260
|
external_provider = null;
|
|
2067
2261
|
}
|
|
2068
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
|
+
}
|
|
2069
2305
|
const manifest = {
|
|
2070
2306
|
version: packageVersion,
|
|
2071
2307
|
profile,
|
|
2072
2308
|
installedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2073
2309
|
components: installedComponents,
|
|
2074
2310
|
...role ? { role } : {},
|
|
2075
|
-
...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 } : {}
|
|
2076
2314
|
};
|
|
2077
2315
|
await writeManifest(targetDir, manifest);
|
|
2078
2316
|
printSuccess(`Initialized with profile "${profile}" \u2014 ${fileCount} files installed`);
|
|
@@ -2156,7 +2394,7 @@ async function runAdd(name, options, targetDir, assetsDir, packageVersion) {
|
|
|
2156
2394
|
let fileCount = 0;
|
|
2157
2395
|
const installedFiles = [];
|
|
2158
2396
|
for (const file of component.files) {
|
|
2159
|
-
const srcPath = join5(assetsDir, file);
|
|
2397
|
+
const srcPath = join5(assetsDir, component.sourceOverrides?.[file] ?? file);
|
|
2160
2398
|
if (options.dryRun) {
|
|
2161
2399
|
console.log(` [dry-run] Would copy: ${file}`);
|
|
2162
2400
|
} else {
|
|
@@ -2199,7 +2437,7 @@ async function runAdd(name, options, targetDir, assetsDir, packageVersion) {
|
|
|
2199
2437
|
import { join as join7 } from "path";
|
|
2200
2438
|
|
|
2201
2439
|
// src/modules/hub.ts
|
|
2202
|
-
import { confirm as confirm2, select as select3 } from "@inquirer/prompts";
|
|
2440
|
+
import { confirm as confirm2, select as select3, input as input3 } from "@inquirer/prompts";
|
|
2203
2441
|
import { readdir as readdir3, writeFile as writeFile5 } from "fs/promises";
|
|
2204
2442
|
import { join as join6 } from "path";
|
|
2205
2443
|
var MANIFEST_RETROFITS = [
|
|
@@ -2220,7 +2458,7 @@ var MANIFEST_RETROFITS = [
|
|
|
2220
2458
|
// Mirrors init.ts's Step 5b branching exactly (EF-14 D5): docs-only
|
|
2221
2459
|
// profiles get a silent default, full/lite get asked.
|
|
2222
2460
|
ask: async (manifest) => {
|
|
2223
|
-
if (manifest.profile === "governance-only" || manifest.profile === "methodology") {
|
|
2461
|
+
if (manifest.profile === "governance-only" || manifest.profile === "methodology" || manifest.profile === "docs") {
|
|
2224
2462
|
return { tracking_mode: "internal", external_provider: null };
|
|
2225
2463
|
}
|
|
2226
2464
|
const tracking_mode = await select3({
|
|
@@ -2243,6 +2481,64 @@ var MANIFEST_RETROFITS = [
|
|
|
2243
2481
|
}
|
|
2244
2482
|
return { tracking_mode, external_provider: null };
|
|
2245
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
|
+
}
|
|
2246
2542
|
}
|
|
2247
2543
|
];
|
|
2248
2544
|
async function runManifestRetrofits(manifest) {
|
|
@@ -2307,6 +2603,25 @@ async function offerRegistryScaffold(targetDir, businessRoot) {
|
|
|
2307
2603
|
}
|
|
2308
2604
|
|
|
2309
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
|
+
}
|
|
2310
2625
|
async function runUpdate(options, targetDir, assetsDir, packageVersion) {
|
|
2311
2626
|
let manifest;
|
|
2312
2627
|
try {
|
|
@@ -2359,7 +2674,8 @@ async function runUpdate(options, targetDir, assetsDir, packageVersion) {
|
|
|
2359
2674
|
}
|
|
2360
2675
|
let fileCount = 0;
|
|
2361
2676
|
const updatedComponents = [];
|
|
2362
|
-
|
|
2677
|
+
const componentsToUpdate = migrateLegacyComponents(manifest.components);
|
|
2678
|
+
for (const installed of componentsToUpdate) {
|
|
2363
2679
|
let component;
|
|
2364
2680
|
try {
|
|
2365
2681
|
component = getComponent(installed.name);
|
|
@@ -2373,8 +2689,15 @@ async function runUpdate(options, targetDir, assetsDir, packageVersion) {
|
|
|
2373
2689
|
}
|
|
2374
2690
|
const updatedFiles = [];
|
|
2375
2691
|
for (const file of component.files) {
|
|
2376
|
-
const srcPath = join7(assetsDir, file);
|
|
2377
|
-
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
|
+
}
|
|
2378
2701
|
if (preserveExisting) {
|
|
2379
2702
|
console.log(` ${options.dryRun ? "[dry-run] Would preserve" : "Preserved"}: ${file}`);
|
|
2380
2703
|
} else if (options.dryRun) {
|
|
@@ -2384,7 +2707,9 @@ async function runUpdate(options, targetDir, assetsDir, packageVersion) {
|
|
|
2384
2707
|
await copyFile(srcPath, targetDir, file, { force: true });
|
|
2385
2708
|
fileCount++;
|
|
2386
2709
|
}
|
|
2387
|
-
|
|
2710
|
+
if (!preserveExisting || installed.files.includes(file)) {
|
|
2711
|
+
updatedFiles.push(file);
|
|
2712
|
+
}
|
|
2388
2713
|
}
|
|
2389
2714
|
for (const dir of component.directories ?? []) {
|
|
2390
2715
|
const srcDirPath = join7(assetsDir, dir);
|
|
@@ -2415,7 +2740,16 @@ async function runUpdate(options, targetDir, assetsDir, packageVersion) {
|
|
|
2415
2740
|
}
|
|
2416
2741
|
return;
|
|
2417
2742
|
}
|
|
2418
|
-
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
|
+
}
|
|
2419
2753
|
const updated = {
|
|
2420
2754
|
...manifest,
|
|
2421
2755
|
version: packageVersion,
|
|
@@ -2963,7 +3297,7 @@ program.name("spell").description("Arcane framework CLI \u2014 scaffold and mana
|
|
|
2963
3297
|
await printWelcome(pkg.version, process.cwd());
|
|
2964
3298
|
program.outputHelp();
|
|
2965
3299
|
});
|
|
2966
|
-
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(
|
|
2967
3301
|
async (opts) => {
|
|
2968
3302
|
await runInit(
|
|
2969
3303
|
{
|