@lsctech/polaris 0.3.28 → 0.4.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/agent-plugin/args.js +77 -0
- package/dist/agent-plugin/claude-generator.js +158 -0
- package/dist/agent-plugin/commands.js +73 -0
- package/dist/agent-plugin/help.js +69 -0
- package/dist/agent-plugin/sync.js +108 -0
- package/dist/cli/adopt-approve.js +124 -4
- package/dist/cli/adopt-assets.js +9 -1
- package/dist/cli/adopt-command.js +123 -11
- package/dist/cli/adopt-instructions.js +17 -2
- package/dist/cli/adopt-smartdocs.js +9 -0
- package/dist/cli/adoption-context.js +46 -0
- package/dist/cli/adoption-plan.js +105 -12
- package/dist/cli/agent-setup.js +47 -0
- package/dist/cli/index.js +14 -0
- package/dist/cli/init.js +69 -1
- package/dist/cli/setup-interview/generate.js +380 -0
- package/dist/cli/setup-interview/report.js +138 -0
- package/dist/cli/setup-interview/runner.js +105 -0
- package/dist/cli/setup-interview/schema.js +16 -0
- package/dist/cli/setup-interview/store.js +73 -0
- package/dist/finalize/index.js +5 -4
- package/dist/loop/adapters/foreman-dispatch.js +65 -0
- package/dist/loop/adapters/index.js +3 -1
- package/dist/loop/dispatch.js +8 -2
- package/dist/loop/simplicity.js +34 -0
- package/dist/loop/worker-packet.js +2 -1
- package/dist/loop/worker-prompt.js +28 -0
- package/dist/skill-packet/generator.js +149 -1
- package/dist/workspace/.polaris/skills/polaris-tools/tools.js +31 -22
- package/package.json +2 -2
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.validateSlashCommandArgs = validateSlashCommandArgs;
|
|
4
|
+
/**
|
|
5
|
+
* Validate positional arguments against the command's declared arg spec.
|
|
6
|
+
*
|
|
7
|
+
* Rules:
|
|
8
|
+
* - `--help` / `-h` anywhere in argv returns a help request (no validation).
|
|
9
|
+
* - Required positional arguments must be present.
|
|
10
|
+
* - No more positional arguments than declared are accepted.
|
|
11
|
+
* - Empty strings are treated as a type mismatch.
|
|
12
|
+
*/
|
|
13
|
+
function validateSlashCommandArgs(command, argv = []) {
|
|
14
|
+
const help = argv.includes("--help") || argv.includes("-h");
|
|
15
|
+
if (help) {
|
|
16
|
+
return { ok: true, value: { command, positional: [], help: true } };
|
|
17
|
+
}
|
|
18
|
+
const positional = argv.filter((arg) => !arg.startsWith("-"));
|
|
19
|
+
const requiredCount = command.args.filter((arg) => arg.required).length;
|
|
20
|
+
const maxCount = command.args.length;
|
|
21
|
+
if (positional.length < requiredCount) {
|
|
22
|
+
return {
|
|
23
|
+
ok: false,
|
|
24
|
+
error: {
|
|
25
|
+
kind: "arity",
|
|
26
|
+
message: `/${command.name} requires ${requiredCount} positional argument(s) but received ${positional.length}.`,
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
if (positional.length > maxCount) {
|
|
31
|
+
return {
|
|
32
|
+
ok: false,
|
|
33
|
+
error: {
|
|
34
|
+
kind: "arity",
|
|
35
|
+
message: `/${command.name} accepts at most ${maxCount} positional argument(s) but received ${positional.length}.`,
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
for (const value of positional) {
|
|
40
|
+
if (value.trim() === "") {
|
|
41
|
+
return {
|
|
42
|
+
ok: false,
|
|
43
|
+
error: {
|
|
44
|
+
kind: "type",
|
|
45
|
+
message: `Arguments to /${command.name} must be non-empty strings.`,
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
// ponytail: support optional typed args (e.g. number, enum) when the manifest
|
|
51
|
+
// adds a `type` field; for now only string/empty-string checks are required.
|
|
52
|
+
if (hasTypedArgs(command.args)) {
|
|
53
|
+
for (let i = 0; i < positional.length; i++) {
|
|
54
|
+
const arg = command.args[i];
|
|
55
|
+
const value = positional[i];
|
|
56
|
+
if (arg.type && !validateArgType(value, arg.type)) {
|
|
57
|
+
return {
|
|
58
|
+
ok: false,
|
|
59
|
+
error: {
|
|
60
|
+
kind: "type",
|
|
61
|
+
message: `Argument \`${arg.name}\` for /${command.name} must be a valid ${arg.type}.`,
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return { ok: true, value: { command, positional, help: false } };
|
|
68
|
+
}
|
|
69
|
+
function hasTypedArgs(args) {
|
|
70
|
+
return args.some((arg) => arg.type !== undefined);
|
|
71
|
+
}
|
|
72
|
+
function validateArgType(value, type) {
|
|
73
|
+
if (type === "identifier") {
|
|
74
|
+
return /^[A-Za-z0-9_-]+$/.test(value);
|
|
75
|
+
}
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.SHIM_VERSION = void 0;
|
|
37
|
+
exports.generateClaudeShim = generateClaudeShim;
|
|
38
|
+
exports.generateAllClaudeShims = generateAllClaudeShims;
|
|
39
|
+
const fs = __importStar(require("fs"));
|
|
40
|
+
const path = __importStar(require("path"));
|
|
41
|
+
const commands_js_1 = require("./commands.js");
|
|
42
|
+
/**
|
|
43
|
+
* Host-specific shim generator for Claude Code (.claude/commands/).
|
|
44
|
+
*
|
|
45
|
+
* Reads the neutral SLASH_COMMANDS manifest and writes one
|
|
46
|
+
* `.claude/commands/polaris-<verb>.md` file per verb.
|
|
47
|
+
*
|
|
48
|
+
* Skill-backed verbs route through the existing packet+chain path:
|
|
49
|
+
* 1. Read .polaris/skills/<skill>/SKILL.md
|
|
50
|
+
* 2. Run `polaris skill packet <skill>`
|
|
51
|
+
* 3. Execute the chain
|
|
52
|
+
*
|
|
53
|
+
* CLI-backed verbs delegate directly to the real CLI command.
|
|
54
|
+
*
|
|
55
|
+
* Version stamp: each shim carries a `<!-- polaris-shim-version: <version> -->` comment
|
|
56
|
+
* so the sync command (child 4) can detect drift.
|
|
57
|
+
*/
|
|
58
|
+
/** Version string baked into every generated shim. */
|
|
59
|
+
exports.SHIM_VERSION = "1";
|
|
60
|
+
/**
|
|
61
|
+
* Generate the markdown body for a Claude Code slash-command shim.
|
|
62
|
+
* Host specifics are isolated here; the manifest itself is neutral.
|
|
63
|
+
*/
|
|
64
|
+
function generateClaudeShim(command) {
|
|
65
|
+
const argUsage = command.args.length > 0
|
|
66
|
+
? command.args.map((a) => (a.required ? `<${a.name}>` : `[${a.name}]`)).join(" ")
|
|
67
|
+
: "";
|
|
68
|
+
const usage = argUsage ? `/${command.name} ${argUsage}` : `/${command.name}`;
|
|
69
|
+
if (command.kind === "skill") {
|
|
70
|
+
const argLines = command.args.length > 0
|
|
71
|
+
? command.args
|
|
72
|
+
.map((a) => `- \`${a.name}\`${a.required ? " (required)" : " (optional)"} — ${a.description}`)
|
|
73
|
+
.join("\n")
|
|
74
|
+
: "None.";
|
|
75
|
+
return `<!-- polaris-shim-version: ${exports.SHIM_VERSION} -->
|
|
76
|
+
# /${command.name}
|
|
77
|
+
|
|
78
|
+
${command.description}
|
|
79
|
+
|
|
80
|
+
## Usage
|
|
81
|
+
|
|
82
|
+
\`\`\`text
|
|
83
|
+
${usage}
|
|
84
|
+
\`\`\`
|
|
85
|
+
|
|
86
|
+
## Arguments
|
|
87
|
+
|
|
88
|
+
${argLines}
|
|
89
|
+
|
|
90
|
+
## Routing
|
|
91
|
+
|
|
92
|
+
This slash command is a shim around the **${command.skill}** Polaris skill packet.
|
|
93
|
+
It does not implement a parallel runtime — it routes through the existing packet+chain path.
|
|
94
|
+
|
|
95
|
+
See \`${command.routing}\` for the full routing protocol and skill directory resolution.
|
|
96
|
+
|
|
97
|
+
## Execution
|
|
98
|
+
|
|
99
|
+
1. Look up \`/${command.name}\` in \`${command.routing}\` to find the target skill directory.
|
|
100
|
+
2. Read \`.polaris/skills/<target-skill>/SKILL.md\` — it is the authoritative instruction source.
|
|
101
|
+
3. Run the skill bootloader:
|
|
102
|
+
\`\`\`bash
|
|
103
|
+
polaris skill packet ${command.skill}${command.args.length > 0 ? " $ARGUMENTS" : ""}
|
|
104
|
+
\`\`\`
|
|
105
|
+
Do not begin work until a packet is returned.
|
|
106
|
+
If no packet is produced, stop and report: **Polaris could not authorize this run.**
|
|
107
|
+
4. Execute the chain as instructed in the packet.
|
|
108
|
+
`;
|
|
109
|
+
}
|
|
110
|
+
// CLI-backed command
|
|
111
|
+
const argLines = command.args.length > 0
|
|
112
|
+
? command.args
|
|
113
|
+
.map((a) => `- \`${a.name}\`${a.required ? " (required)" : " (optional)"} — ${a.description}`)
|
|
114
|
+
.join("\n")
|
|
115
|
+
: "None.";
|
|
116
|
+
return `<!-- polaris-shim-version: ${exports.SHIM_VERSION} -->
|
|
117
|
+
# /${command.name}
|
|
118
|
+
|
|
119
|
+
${command.description}
|
|
120
|
+
|
|
121
|
+
## Usage
|
|
122
|
+
|
|
123
|
+
\`\`\`text
|
|
124
|
+
${usage}
|
|
125
|
+
\`\`\`
|
|
126
|
+
|
|
127
|
+
## Arguments
|
|
128
|
+
|
|
129
|
+
${argLines}
|
|
130
|
+
|
|
131
|
+
## Execution
|
|
132
|
+
|
|
133
|
+
Run the following CLI command:
|
|
134
|
+
|
|
135
|
+
\`\`\`bash
|
|
136
|
+
${command.command}
|
|
137
|
+
\`\`\`
|
|
138
|
+
`;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Write all Claude Code shim files to `outDir` (default: `.claude/commands`).
|
|
142
|
+
* Creates the directory if it does not exist.
|
|
143
|
+
* Returns the list of files written.
|
|
144
|
+
*/
|
|
145
|
+
function generateAllClaudeShims(outDir = ".claude/commands") {
|
|
146
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
147
|
+
const written = [];
|
|
148
|
+
const seen = new Set();
|
|
149
|
+
for (const command of commands_js_1.SLASH_COMMANDS) {
|
|
150
|
+
if (seen.has(command.name))
|
|
151
|
+
continue;
|
|
152
|
+
seen.add(command.name);
|
|
153
|
+
const filePath = path.join(outDir, `${command.name}.md`);
|
|
154
|
+
fs.writeFileSync(filePath, generateClaudeShim(command), "utf8");
|
|
155
|
+
written.push(filePath);
|
|
156
|
+
}
|
|
157
|
+
return written;
|
|
158
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SLASH_COMMAND_BY_NAME = exports.SLASH_COMMANDS = void 0;
|
|
4
|
+
exports.resolveSlashCommand = resolveSlashCommand;
|
|
5
|
+
exports.SLASH_COMMANDS = [
|
|
6
|
+
{
|
|
7
|
+
name: "polaris-run",
|
|
8
|
+
kind: "skill",
|
|
9
|
+
skill: "run",
|
|
10
|
+
routing: ".polaris/skills/ROUTING.md",
|
|
11
|
+
args: [
|
|
12
|
+
{
|
|
13
|
+
name: "cluster_id",
|
|
14
|
+
required: true,
|
|
15
|
+
description: "Cluster ID to execute (e.g., POL-257)",
|
|
16
|
+
},
|
|
17
|
+
],
|
|
18
|
+
description: "Execute a Polaris run cluster via the Foreman skill packet",
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
name: "polaris-analyze",
|
|
22
|
+
kind: "skill",
|
|
23
|
+
skill: "analyze",
|
|
24
|
+
routing: ".polaris/skills/ROUTING.md",
|
|
25
|
+
args: [
|
|
26
|
+
{
|
|
27
|
+
name: "cluster_id",
|
|
28
|
+
required: true,
|
|
29
|
+
description: "Cluster ID to analyze (e.g., POL-257)",
|
|
30
|
+
},
|
|
31
|
+
],
|
|
32
|
+
description: "Analyze a cluster and produce an implementation plan via the Analyst skill packet",
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
name: "polaris-init",
|
|
36
|
+
kind: "cli",
|
|
37
|
+
command: "polaris init",
|
|
38
|
+
args: [],
|
|
39
|
+
description: "Initialize a new Polaris workspace",
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
name: "polaris-adopt",
|
|
43
|
+
kind: "cli",
|
|
44
|
+
command: "polaris adopt",
|
|
45
|
+
args: [],
|
|
46
|
+
description: "Adopt Polaris into an existing repository",
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
name: "polaris-reconcile",
|
|
50
|
+
kind: "skill",
|
|
51
|
+
skill: "triage",
|
|
52
|
+
routing: ".polaris/skills/ROUTING.md",
|
|
53
|
+
args: [
|
|
54
|
+
{
|
|
55
|
+
name: "target",
|
|
56
|
+
required: true,
|
|
57
|
+
description: "Reconciliation target (e.g., smartdocs or a cluster ID)",
|
|
58
|
+
},
|
|
59
|
+
],
|
|
60
|
+
description: "Reconcile project cognition via the triage skill packet",
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
name: "polaris-status",
|
|
64
|
+
kind: "cli",
|
|
65
|
+
command: "polaris status",
|
|
66
|
+
args: [],
|
|
67
|
+
description: "Print the current Polaris loop run state summary",
|
|
68
|
+
},
|
|
69
|
+
];
|
|
70
|
+
exports.SLASH_COMMAND_BY_NAME = Object.fromEntries(exports.SLASH_COMMANDS.map((command) => [command.name, command]));
|
|
71
|
+
function resolveSlashCommand(name) {
|
|
72
|
+
return exports.SLASH_COMMAND_BY_NAME[name];
|
|
73
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.generateUsage = generateUsage;
|
|
4
|
+
exports.generateHelp = generateHelp;
|
|
5
|
+
exports.generateErrorMessage = generateErrorMessage;
|
|
6
|
+
exports.generateResponse = generateResponse;
|
|
7
|
+
/**
|
|
8
|
+
* Help and error-message generation for slash-command invocations.
|
|
9
|
+
*
|
|
10
|
+
* Produces `--help`-style output directly from the neutral manifest so the
|
|
11
|
+
* same text can be used by any host shim. Error messages include the same
|
|
12
|
+
* help block so users see both the failure and the correct usage.
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Build a usage line from the command's arg spec.
|
|
16
|
+
* Required args are shown as `<name>`; optional as `[name]`.
|
|
17
|
+
*/
|
|
18
|
+
function generateUsage(command) {
|
|
19
|
+
const argUsage = command.args
|
|
20
|
+
.map((arg) => (arg.required ? `<${arg.name}>` : `[${arg.name}]`))
|
|
21
|
+
.join(" ");
|
|
22
|
+
return argUsage ? `/${command.name} ${argUsage}` : `/${command.name}`;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Generate `--help`-style output for a single manifest verb.
|
|
26
|
+
*/
|
|
27
|
+
function generateHelp(command) {
|
|
28
|
+
const argLines = command.args.length > 0
|
|
29
|
+
? command.args
|
|
30
|
+
.map((arg) => `- \`${arg.name}\`${arg.required ? " (required)" : " (optional)"} — ${arg.description}`)
|
|
31
|
+
.join("\n")
|
|
32
|
+
: "None.";
|
|
33
|
+
return `/${command.name}
|
|
34
|
+
|
|
35
|
+
${command.description}
|
|
36
|
+
|
|
37
|
+
Usage:
|
|
38
|
+
|
|
39
|
+
\`\`\`
|
|
40
|
+
${generateUsage(command)}
|
|
41
|
+
\`\`\`
|
|
42
|
+
|
|
43
|
+
Arguments:
|
|
44
|
+
|
|
45
|
+
${argLines}
|
|
46
|
+
|
|
47
|
+
Use \`--help\` or \`-h\` to show this message.`;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Generate an error message that includes the command's help block.
|
|
51
|
+
*/
|
|
52
|
+
function generateErrorMessage(command, error) {
|
|
53
|
+
return `Error: ${error.message}
|
|
54
|
+
|
|
55
|
+
${generateHelp(command)}`;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Convenience helper that returns the rendered message for a validation result.
|
|
59
|
+
* Returns `null` when the input is valid and no help/error text is needed.
|
|
60
|
+
*/
|
|
61
|
+
function generateResponse(command, result) {
|
|
62
|
+
if (!result.ok) {
|
|
63
|
+
return generateErrorMessage(command, result.error);
|
|
64
|
+
}
|
|
65
|
+
if (result.value.help) {
|
|
66
|
+
return generateHelp(command);
|
|
67
|
+
}
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.detectShimDrift = detectShimDrift;
|
|
37
|
+
exports.syncShims = syncShims;
|
|
38
|
+
const fs = __importStar(require("fs"));
|
|
39
|
+
const path = __importStar(require("path"));
|
|
40
|
+
const commands_js_1 = require("./commands.js");
|
|
41
|
+
const claude_generator_js_1 = require("./claude-generator.js");
|
|
42
|
+
/**
|
|
43
|
+
* Workspace sync and versioning for generated agent-plugin shims.
|
|
44
|
+
*
|
|
45
|
+
* Responsibilities:
|
|
46
|
+
* 1. Detect drift between on-disk shims and the current SLASH_COMMANDS manifest
|
|
47
|
+
* / ROUTING.md (stale version stamps, missing files, orphaned files).
|
|
48
|
+
* 2. Regenerate and re-stamp all shims when called.
|
|
49
|
+
* 3. Integrate with the asset-install path (called from adopt-assets.ts).
|
|
50
|
+
*/
|
|
51
|
+
/** Pattern used to read the version stamp from a shim file. */
|
|
52
|
+
const VERSION_STAMP_RE = /<!--\s*polaris-shim-version:\s*(\S+)\s*-->/;
|
|
53
|
+
/**
|
|
54
|
+
* Inspect `outDir` and return a drift report comparing on-disk shims against
|
|
55
|
+
* the current SLASH_COMMANDS manifest and SHIM_VERSION.
|
|
56
|
+
*/
|
|
57
|
+
function detectShimDrift(outDir = ".claude/commands") {
|
|
58
|
+
const stale = [];
|
|
59
|
+
const missing = [];
|
|
60
|
+
const orphaned = [];
|
|
61
|
+
const expectedNames = new Set(commands_js_1.SLASH_COMMANDS.map((c) => `${c.name}.md`));
|
|
62
|
+
// Check each manifest command against the on-disk file
|
|
63
|
+
for (const command of commands_js_1.SLASH_COMMANDS) {
|
|
64
|
+
const filePath = path.join(outDir, `${command.name}.md`);
|
|
65
|
+
if (!fs.existsSync(filePath)) {
|
|
66
|
+
missing.push(command.name);
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
const content = fs.readFileSync(filePath, "utf8");
|
|
70
|
+
const match = VERSION_STAMP_RE.exec(content);
|
|
71
|
+
if (!match || match[1] !== claude_generator_js_1.SHIM_VERSION) {
|
|
72
|
+
stale.push(command.name);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
// Check for orphaned shim files (on disk but not in manifest)
|
|
76
|
+
if (fs.existsSync(outDir)) {
|
|
77
|
+
for (const entry of fs.readdirSync(outDir)) {
|
|
78
|
+
if (!entry.endsWith(".md"))
|
|
79
|
+
continue;
|
|
80
|
+
if (!expectedNames.has(entry)) {
|
|
81
|
+
orphaned.push(entry.replace(/\.md$/, ""));
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
stale,
|
|
87
|
+
missing,
|
|
88
|
+
orphaned,
|
|
89
|
+
hasDrift: stale.length > 0 || missing.length > 0 || orphaned.length > 0,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Regenerate all shims and return what was written plus the prior drift state.
|
|
94
|
+
*
|
|
95
|
+
* This is the primary entry point for both `polaris agent-plugin sync` and
|
|
96
|
+
* the adopt-assets install step.
|
|
97
|
+
*/
|
|
98
|
+
function syncShims(outDir = ".claude/commands") {
|
|
99
|
+
const drift = detectShimDrift(outDir);
|
|
100
|
+
const written = (0, claude_generator_js_1.generateAllClaudeShims)(outDir);
|
|
101
|
+
for (const name of drift.orphaned) {
|
|
102
|
+
const orphanPath = path.join(outDir, `${name}.md`);
|
|
103
|
+
if (fs.existsSync(orphanPath)) {
|
|
104
|
+
fs.rmSync(orphanPath);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return { written, drift };
|
|
108
|
+
}
|
|
@@ -3,6 +3,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.persistApprovedAdoptionPlan = persistApprovedAdoptionPlan;
|
|
4
4
|
exports.logAdoptionApprovalTelemetry = logAdoptionApprovalTelemetry;
|
|
5
5
|
exports.promptApproval = promptApproval;
|
|
6
|
+
exports.promptCategoryApproval = promptCategoryApproval;
|
|
7
|
+
exports.requireApprovalGates = requireApprovalGates;
|
|
6
8
|
const node_fs_1 = require("node:fs");
|
|
7
9
|
const node_path_1 = require("node:path");
|
|
8
10
|
const promises_1 = require("node:readline/promises");
|
|
@@ -36,9 +38,10 @@ async function promptApproval(plan, options = {}) {
|
|
|
36
38
|
const stdout = options.stdout ?? process.stdout;
|
|
37
39
|
const stdin = options.stdin ?? process.stdin;
|
|
38
40
|
const markdownPath = adoptionPlanMarkdownPath(repoRoot);
|
|
39
|
-
const markdown =
|
|
40
|
-
|
|
41
|
-
|
|
41
|
+
const markdown = options.markdown ??
|
|
42
|
+
((0, node_fs_1.existsSync)(markdownPath)
|
|
43
|
+
? (0, node_fs_1.readFileSync)(markdownPath, "utf-8")
|
|
44
|
+
: (0, adoption_plan_js_1.renderAdoptionPlanMarkdown)(plan));
|
|
42
45
|
stdout.write(`${markdown.replace(/\s*$/, "")}\n\n`);
|
|
43
46
|
const rl = (0, promises_1.createInterface)({
|
|
44
47
|
input: stdin,
|
|
@@ -52,9 +55,126 @@ async function promptApproval(plan, options = {}) {
|
|
|
52
55
|
rl.close();
|
|
53
56
|
}
|
|
54
57
|
if (response.trim().toLowerCase() === "y") {
|
|
55
|
-
|
|
58
|
+
const approvalNow = options.now ?? new Date();
|
|
59
|
+
if (options.persist) {
|
|
60
|
+
options.persist(plan, repoRoot, approvalNow);
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
persistApprovedAdoptionPlan(plan, repoRoot, approvalNow);
|
|
64
|
+
}
|
|
56
65
|
return true;
|
|
57
66
|
}
|
|
58
67
|
stdout.write("Adoption aborted: explicit approval required.\n");
|
|
59
68
|
return false;
|
|
60
69
|
}
|
|
70
|
+
/** Map of category → step categories in AdoptionStep */
|
|
71
|
+
const CATEGORY_STEP_MAP = {
|
|
72
|
+
"doc-movement": ["smartdocs-migrate"],
|
|
73
|
+
"instruction-file": ["instruction-refactor"],
|
|
74
|
+
"graph-root": ["cognition-generate", "atlas-generate"],
|
|
75
|
+
"route-scaffold": ["scaffold"],
|
|
76
|
+
};
|
|
77
|
+
const CATEGORY_LABELS = {
|
|
78
|
+
"doc-movement": "Document Movement",
|
|
79
|
+
"instruction-file": "Instruction-File Changes",
|
|
80
|
+
"graph-root": "Graph-Root / Cognition Changes",
|
|
81
|
+
"route-scaffold": "Route Scaffolding",
|
|
82
|
+
};
|
|
83
|
+
/**
|
|
84
|
+
* Render a compact diff preview for the given steps.
|
|
85
|
+
*/
|
|
86
|
+
function renderStepDiff(steps) {
|
|
87
|
+
if (steps.length === 0)
|
|
88
|
+
return " (no steps)\n";
|
|
89
|
+
return steps
|
|
90
|
+
.map((s) => {
|
|
91
|
+
const src = s.source_path ? ` ${s.source_path} →` : "";
|
|
92
|
+
const dst = s.dest_path ? ` ${s.dest_path}` : "";
|
|
93
|
+
const routing = s.routing ? ` [${s.routing}]` : "";
|
|
94
|
+
return ` ${s.action.toUpperCase()}${src}${dst}${routing} — ${s.description}`;
|
|
95
|
+
})
|
|
96
|
+
.join("\n") + "\n";
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Ask the operator for approval of a single mutation category.
|
|
100
|
+
* Returns true if approved, false if declined.
|
|
101
|
+
* Logs the decision via adoption telemetry.
|
|
102
|
+
*/
|
|
103
|
+
async function promptCategoryApproval(category, steps, options = {}) {
|
|
104
|
+
const repoRoot = options.repoRoot ?? process.cwd();
|
|
105
|
+
const stdout = options.stdout ?? process.stdout;
|
|
106
|
+
const stdin = options.stdin ?? process.stdin;
|
|
107
|
+
const label = CATEGORY_LABELS[category];
|
|
108
|
+
const actionableSteps = steps.filter((s) => s.action !== "skip");
|
|
109
|
+
if (actionableSteps.length === 0)
|
|
110
|
+
return true;
|
|
111
|
+
stdout.write(`\n--- ${label} (${actionableSteps.length} step(s)) ---\n`);
|
|
112
|
+
stdout.write(renderStepDiff(actionableSteps));
|
|
113
|
+
const rl = (0, promises_1.createInterface)({ input: stdin, output: stdout });
|
|
114
|
+
let response = "";
|
|
115
|
+
try {
|
|
116
|
+
response = await rl.question(`Approve ${label}? [y/N] `);
|
|
117
|
+
}
|
|
118
|
+
finally {
|
|
119
|
+
rl.close();
|
|
120
|
+
}
|
|
121
|
+
const approved = response.trim().toLowerCase() === "y";
|
|
122
|
+
const now = (options.now ?? new Date()).toISOString();
|
|
123
|
+
logAdoptionApprovalTelemetry(repoRoot, {
|
|
124
|
+
event: "category-approval",
|
|
125
|
+
category,
|
|
126
|
+
approved,
|
|
127
|
+
step_count: actionableSteps.length,
|
|
128
|
+
timestamp: now,
|
|
129
|
+
});
|
|
130
|
+
if (!approved) {
|
|
131
|
+
stdout.write(`Adoption aborted: ${label} approval required.\n`);
|
|
132
|
+
}
|
|
133
|
+
return approved;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Fire approval gates for all four mutation categories in sequence.
|
|
137
|
+
* Returns false (and stops) at the first declined category.
|
|
138
|
+
*
|
|
139
|
+
* For non-interactive runs (no tty + no supplied stdin), returns false immediately
|
|
140
|
+
* unless `options.stdin` is explicitly set (which signals the caller supplied a context stream).
|
|
141
|
+
*/
|
|
142
|
+
async function requireApprovalGates(plan, options = {}) {
|
|
143
|
+
const stdin = options.stdin ?? process.stdin;
|
|
144
|
+
// Non-interactive guard: if stdin is not a TTY and the caller hasn't explicitly supplied one,
|
|
145
|
+
// block before mutation (acceptance criterion 2).
|
|
146
|
+
if (!options.nonInteractiveSafe && stdin === process.stdin && !process.stdin.isTTY) {
|
|
147
|
+
const stdout = options.stdout ?? process.stdout;
|
|
148
|
+
stdout.write("Adoption aborted: non-interactive run requires a supplied context file (--context) or explicit approval.\n");
|
|
149
|
+
logAdoptionApprovalTelemetry(options.repoRoot ?? process.cwd(), {
|
|
150
|
+
event: "category-approval-blocked",
|
|
151
|
+
reason: "non-interactive",
|
|
152
|
+
timestamp: new Date().toISOString(),
|
|
153
|
+
});
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
const categories = [
|
|
157
|
+
"route-scaffold",
|
|
158
|
+
"doc-movement",
|
|
159
|
+
"instruction-file",
|
|
160
|
+
"graph-root",
|
|
161
|
+
];
|
|
162
|
+
for (const category of categories) {
|
|
163
|
+
const stepCategories = CATEGORY_STEP_MAP[category];
|
|
164
|
+
const steps = plan.steps.filter((s) => {
|
|
165
|
+
if (!stepCategories.includes(s.category))
|
|
166
|
+
return false;
|
|
167
|
+
// Doc-movement gate only fires for steps that migrateSmartDocs will execute
|
|
168
|
+
if (category === "doc-movement" && s.routing !== "candidate")
|
|
169
|
+
return false;
|
|
170
|
+
return true;
|
|
171
|
+
});
|
|
172
|
+
// Skip gate if no actionable steps in this category
|
|
173
|
+
if (steps.filter((s) => s.action !== "skip").length === 0)
|
|
174
|
+
continue;
|
|
175
|
+
const approved = await promptCategoryApproval(category, steps, options);
|
|
176
|
+
if (!approved)
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
179
|
+
return true;
|
|
180
|
+
}
|
package/dist/cli/adopt-assets.js
CHANGED
|
@@ -6,6 +6,7 @@ exports.runGraphBuild = runGraphBuild;
|
|
|
6
6
|
const node_fs_1 = require("node:fs");
|
|
7
7
|
const node_child_process_1 = require("node:child_process");
|
|
8
8
|
const node_path_1 = require("node:path");
|
|
9
|
+
const sync_js_1 = require("../agent-plugin/sync.js");
|
|
9
10
|
function isAncestorSymlink(repoRoot, relPath) {
|
|
10
11
|
const parts = relPath.split("/").filter(Boolean);
|
|
11
12
|
for (let i = 0; i < parts.length - 1; i++) {
|
|
@@ -168,7 +169,14 @@ function installWorkspaceAssets(repoRoot, workspaceDir) {
|
|
|
168
169
|
installed.push(rulesRel);
|
|
169
170
|
}
|
|
170
171
|
}
|
|
171
|
-
|
|
172
|
+
// 6. Agent-plugin shims (Claude Code commands)
|
|
173
|
+
const shimOutDir = (0, node_path_1.join)(repoRoot, ".claude", "commands");
|
|
174
|
+
if (isAncestorSymlink(repoRoot, ".claude/commands")) {
|
|
175
|
+
skipped.push(".claude/commands");
|
|
176
|
+
return { installed, alreadyPresent, skipped, conflicted };
|
|
177
|
+
}
|
|
178
|
+
const shimSync = (0, sync_js_1.syncShims)(shimOutDir);
|
|
179
|
+
return { installed, alreadyPresent, skipped, conflicted, shimSync };
|
|
172
180
|
}
|
|
173
181
|
function runGraphBuild(repoRoot) {
|
|
174
182
|
try {
|