@aifabrix/builder 2.54.1 → 2.55.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.
Files changed (210) hide show
  1. package/README.md +8 -0
  2. package/lib/agent-kit/apply-ops.js +82 -0
  3. package/lib/agent-kit/checksums.js +28 -0
  4. package/lib/agent-kit/classify.js +113 -0
  5. package/lib/agent-kit/claude-shim.js +61 -0
  6. package/lib/agent-kit/constants.js +55 -0
  7. package/lib/agent-kit/file-list.js +103 -0
  8. package/lib/agent-kit/host-approve.js +130 -0
  9. package/lib/agent-kit/hosting/github.js +63 -0
  10. package/lib/agent-kit/hosts.js +71 -0
  11. package/lib/agent-kit/init.js +319 -0
  12. package/lib/agent-kit/kit-root.js +17 -0
  13. package/lib/agent-kit/managed-section.js +79 -0
  14. package/lib/agent-kit/package-load.js +45 -0
  15. package/lib/agent-kit/plan-ops.js +69 -0
  16. package/lib/agent-kit/remote-prompts.js +239 -0
  17. package/lib/agent-kit/render-host-markdown.js +40 -0
  18. package/lib/agent-kit/repo-guard.js +47 -0
  19. package/lib/agent-kit/report.js +107 -0
  20. package/lib/agent-kit/run.js +82 -0
  21. package/lib/agent-kit/scm/git.js +122 -0
  22. package/lib/agent-kit/state-file.js +83 -0
  23. package/lib/agent-kit/status.js +50 -0
  24. package/lib/agent-kit/v1-detect.js +81 -0
  25. package/lib/agent-kit/validate-kit.js +87 -0
  26. package/lib/agent-kit/workspace-name.js +40 -0
  27. package/lib/agent-kit/workspace-skeleton.js +103 -0
  28. package/lib/api/role-assistant-test-pack.api.js +86 -0
  29. package/lib/api/work-search.api.js +31 -4
  30. package/lib/api/work.api.js +19 -12
  31. package/lib/app/list-external-systems.js +2 -1
  32. package/lib/app/show-display.js +33 -6
  33. package/lib/app/show.js +25 -2
  34. package/lib/channels/add-channel.js +1 -0
  35. package/lib/channels/approval-guides/claude.js +2 -2
  36. package/lib/channels/channel-add-failure.js +1 -0
  37. package/lib/channels/channel-add-remaining.js +15 -9
  38. package/lib/channels/channel-definition-status.js +115 -0
  39. package/lib/channels/channel-description.js +2 -1
  40. package/lib/channels/channel-install-envelope.js +4 -2
  41. package/lib/channels/channel-integration-generator.js +2 -0
  42. package/lib/channels/channel-runtime-resolver.js +14 -0
  43. package/lib/channels/channel-targets.js +31 -5
  44. package/lib/channels/claude-setup-values.js +4 -1
  45. package/lib/channels/slack-channel-add.js +59 -6
  46. package/lib/channels/slack-mcp-callback.js +51 -0
  47. package/lib/channels/slack-mcp-runtime.js +156 -0
  48. package/lib/channels/slack-provisioning/install-cli.js +14 -2
  49. package/lib/channels/slack-provisioning/with-curl-on-path.js +6 -1
  50. package/lib/cli/index.js +4 -0
  51. package/lib/cli/setup-certification-commands.js +2 -2
  52. package/lib/cli/setup-certification.help.js +3 -1
  53. package/lib/commands/agent-kit-help.js +79 -0
  54. package/lib/commands/agent-kit.js +346 -0
  55. package/lib/commands/channel-add-help.js +5 -5
  56. package/lib/commands/datasource-capability-relate-cli.js +5 -4
  57. package/lib/commands/repair-auth-apply.js +1 -1
  58. package/lib/commands/repair-datasource-key-skip.js +39 -0
  59. package/lib/commands/repair-datasource-keys.js +24 -12
  60. package/lib/commands/repair-rbac.js +16 -10
  61. package/lib/commands/role-assistant-copy-output.js +61 -0
  62. package/lib/commands/role-assistant-copy.js +155 -0
  63. package/lib/commands/role-assistant-test-output.js +8 -9
  64. package/lib/commands/role-assistant-test-result-rows.js +42 -0
  65. package/lib/commands/role-assistant.js +117 -48
  66. package/lib/commands/search.js +143 -0
  67. package/lib/commands/test-trust-external.js +7 -5
  68. package/lib/commands/upload-sidecar-followups.js +4 -1
  69. package/lib/commands/verify-operations-skip-e2e.js +131 -0
  70. package/lib/commands/verify-operations-step-plan.js +6 -33
  71. package/lib/commands/verify-operations-steps.js +3 -1
  72. package/lib/datasource/capability/relate-operations.js +28 -10
  73. package/lib/datasource/capability/run-capability-relate.js +48 -25
  74. package/lib/datasource/datasource-identifier.js +13 -5
  75. package/lib/deployment/deployer-poll-run.js +117 -35
  76. package/lib/external-system/deploy-dataplane-published.js +70 -0
  77. package/lib/external-system/deploy.js +12 -2
  78. package/lib/external-system/download-role-assistant.js +41 -15
  79. package/lib/external-system/download.js +7 -1
  80. package/lib/generator/wizard-datasource-key.js +54 -0
  81. package/lib/generator/wizard.js +55 -54
  82. package/lib/integration-definition/apply-datasource-keys.js +75 -0
  83. package/lib/integration-definition/apply-relationships.js +195 -0
  84. package/lib/integration-definition/apply-static-enterprise.js +229 -0
  85. package/lib/integration-definition/apply-system-file.js +71 -0
  86. package/lib/integration-definition/apply-test-endpoint.js +89 -0
  87. package/lib/integration-definition/apply-viewpoint-defaults.js +157 -0
  88. package/lib/integration-definition/apply.js +78 -59
  89. package/lib/integration-definition/enrich-openapi-operations.js +36 -10
  90. package/lib/integration-definition/enrich-openapi-pick.js +87 -0
  91. package/lib/lifecycle/role-assistant-governance.js +56 -21
  92. package/lib/programmatic/capability-registry.js +1 -0
  93. package/lib/programmatic/index.js +2 -0
  94. package/lib/programmatic/openapi-apply-result-schemas.js +8 -0
  95. package/lib/programmatic/openapi-channel-install-schemas.js +4 -1
  96. package/lib/programmatic/openapi-component-schemas.js +1 -0
  97. package/lib/programmatic/openapi-descriptions.js +9 -3
  98. package/lib/programmatic/openapi-envelope-examples.js +7 -0
  99. package/lib/programmatic/openapi-operation-bindings.js +5 -0
  100. package/lib/programmatic/openapi-request-schemas.js +5 -0
  101. package/lib/programmatic/openapi-schema-descriptions-channel.js +8 -5
  102. package/lib/programmatic/openapi-schema-descriptions-operations.js +19 -0
  103. package/lib/programmatic/openapi-spec.js +1 -0
  104. package/lib/programmatic/request-timeout.js +3 -0
  105. package/lib/programmatic/route-handler-map.js +7 -0
  106. package/lib/programmatic/run-channel-catalog.js +50 -8
  107. package/lib/programmatic/run-channel-install-options.js +5 -1
  108. package/lib/programmatic/run-download.js +41 -0
  109. package/lib/protection/auth-context.js +3 -1
  110. package/lib/protection/protection-display.js +3 -2
  111. package/lib/protection/report-exit.js +50 -0
  112. package/lib/protection/run-commands.js +2 -0
  113. package/lib/role-assistant/package-validate.js +175 -0
  114. package/lib/role-assistant/test-case-assertion-row.js +36 -0
  115. package/lib/role-assistant/test-case-copy-bind.js +77 -0
  116. package/lib/role-assistant/test-case-copy-expected.js +127 -0
  117. package/lib/role-assistant/test-case-copy-from-work.js +90 -0
  118. package/lib/role-assistant/test-case-copy-interactions.js +67 -0
  119. package/lib/role-assistant/test-case-copy.js +119 -0
  120. package/lib/role-assistant/test-case-expected-catalog.js +135 -0
  121. package/lib/role-assistant/test-case-validate.js +88 -0
  122. package/lib/role-assistant/test-cases-bid.js +117 -4
  123. package/lib/role-assistant/test-cases-evaluate.js +137 -30
  124. package/lib/role-assistant/test-cases-honesty.js +155 -6
  125. package/lib/role-assistant/test-cases-lifecycle.js +291 -0
  126. package/lib/role-assistant/test-cases-search-graph.js +122 -0
  127. package/lib/role-assistant/test-cases-search.js +20 -2
  128. package/lib/role-assistant/test-cases.js +34 -168
  129. package/lib/role-assistant/test-pack-map.js +180 -0
  130. package/lib/role-assistant/test-pack-sync.js +187 -0
  131. package/lib/role-assistant/test-runner-debug.js +54 -0
  132. package/lib/role-assistant/test-runner-respond-body.js +36 -0
  133. package/lib/role-assistant/test-runner-search.js +20 -5
  134. package/lib/role-assistant/test-runner-start-payload.js +63 -10
  135. package/lib/role-assistant/test-runner-workhub-answers.js +45 -16
  136. package/lib/role-assistant/test-runner-workhub.js +45 -21
  137. package/lib/role-assistant/test-runner.js +50 -10
  138. package/lib/schema/external-datasource.schema.json +36 -5
  139. package/lib/schema/external-system.schema.json +20 -2
  140. package/lib/schema/role-assistant-test-case.schema.json +408 -0
  141. package/lib/search/dataplane-context.js +23 -0
  142. package/lib/search/search-args.js +211 -0
  143. package/lib/search/search-display.js +238 -0
  144. package/lib/search/search-errors.js +50 -0
  145. package/lib/search/search-prompts.js +119 -0
  146. package/lib/search/search-roles.js +85 -0
  147. package/lib/search/search-run.js +241 -0
  148. package/lib/search/search-skills.js +198 -0
  149. package/lib/utils/authentication-slot-show.js +153 -0
  150. package/lib/utils/credential-display.js +43 -1
  151. package/lib/utils/external-system-readiness-compact-blocks.js +5 -1
  152. package/lib/utils/external-system-readiness-compact-display.js +30 -1
  153. package/lib/utils/external-system-validators.js +2 -1
  154. package/lib/utils/help-builder.js +3 -1
  155. package/lib/utils/schema-loader.js +37 -0
  156. package/lib/validation/validate-external-file.js +5 -1
  157. package/lib/validation/validate.js +3 -2
  158. package/package.json +1 -1
  159. package/templates/agent-kit/adapters/claude/nested.CLAUDE.md +1 -0
  160. package/templates/agent-kit/adapters/claude/root.CLAUDE.md +5 -0
  161. package/templates/agent-kit/adapters/cursor/commands/cust-0-plan.md +5 -0
  162. package/templates/agent-kit/adapters/cursor/commands/cust-1-validate.md +5 -0
  163. package/templates/agent-kit/adapters/cursor/commands/cust-2-build.md +5 -0
  164. package/templates/agent-kit/adapters/cursor/commands/cust-3-push.md +5 -0
  165. package/templates/agent-kit/adapters/cursor/commands/cust-biz-1-validate.md +5 -0
  166. package/templates/agent-kit/adapters/cursor/commands/cust-biz-2-build.md +5 -0
  167. package/templates/agent-kit/adapters/cursor/commands/cust-biz-3-prove.md +5 -0
  168. package/templates/agent-kit/agent-kit.yaml +45 -0
  169. package/templates/agent-kit/instructions/AGENTKIT.md +83 -0
  170. package/templates/agent-kit/instructions/integration.AGENTS.md +25 -0
  171. package/templates/agent-kit/instructions/role-assistant.AGENTS.md +22 -0
  172. package/templates/agent-kit/instructions/root.AGENTS.md +37 -0
  173. package/templates/agent-kit/parity-inventory.yaml +188 -0
  174. package/templates/agent-kit/skills/aifabrix-connected-system/SKILL.md +58 -0
  175. package/templates/agent-kit/skills/aifabrix-connected-system/references/contract-checklist.md +40 -0
  176. package/templates/agent-kit/skills/aifabrix-connected-system/references/delivery-gates.md +54 -0
  177. package/templates/agent-kit/skills/aifabrix-connected-system/references/production-safety.md +35 -0
  178. package/templates/agent-kit/skills/aifabrix-connected-system/scripts/reserved-system-key.js +19 -0
  179. package/templates/agent-kit/skills/aifabrix-plan/SKILL.md +51 -0
  180. package/templates/agent-kit/skills/aifabrix-plan/references/challenge-checklist.md +108 -0
  181. package/templates/agent-kit/skills/aifabrix-plan/references/interaction.md +34 -0
  182. package/templates/agent-kit/skills/aifabrix-plan/references/plan-template.md +182 -0
  183. package/templates/agent-kit/skills/aifabrix-prove/SKILL.md +50 -0
  184. package/templates/agent-kit/skills/aifabrix-prove/references/demo-template.md +65 -0
  185. package/templates/agent-kit/skills/aifabrix-prove/references/evidence-lifecycle.md +68 -0
  186. package/templates/agent-kit/skills/aifabrix-prove/references/learning-template.md +66 -0
  187. package/templates/agent-kit/skills/aifabrix-role-assistant/SKILL.md +42 -0
  188. package/templates/agent-kit/skills/aifabrix-role-assistant/references/package-boundary.md +21 -0
  189. package/templates/agent-kit/skills/shared/hosts.md +78 -0
  190. package/templates/agent-kit/workspace/.cursor/plans/README.md +22 -0
  191. package/templates/agent-kit/workspace/LICENSE.mit +21 -0
  192. package/templates/agent-kit/workspace/README.md +89 -0
  193. package/templates/applications/builder-api/Dockerfile +3 -1
  194. package/templates/applications/dataplane/application.yaml +2 -2
  195. package/templates/applications/dataplane/env.template +1 -1
  196. package/templates/applications/dataplane/rbac.yaml +28 -0
  197. package/templates/applications/keycloak/env.template +3 -3
  198. package/templates/applications/miso-controller/application.yaml +1 -1
  199. package/templates/applications/miso-controller/env.template +8 -9
  200. package/templates/channels/claude/TROUBLESHOOTING.md +3 -0
  201. package/templates/channels/claude/channel.json +1 -1
  202. package/templates/channels/claude/install.template.md.hbs +1 -0
  203. package/templates/channels/claude/integration/claude-system.json +1 -1
  204. package/templates/channels/slack/channel.json +11 -0
  205. package/templates/channels/slack/install.template.md.hbs +9 -0
  206. package/templates/channels/slack/integration/slack-system.json +11 -0
  207. package/templates/channels/slack/manifest.template.json.hbs +6 -3
  208. package/templates/channels/teams-bot/channel.json +1 -0
  209. package/templates/python/docker-compose.hbs +14 -0
  210. package/templates/typescript/docker-compose.hbs +14 -0
@@ -0,0 +1,239 @@
1
+ /**
2
+ * Interactive prompts for agent-kit init and remote create.
3
+ * @fileoverview TTY host, org, visibility, and confirm; injectable for tests
4
+ */
5
+
6
+ 'use strict';
7
+
8
+ const { execFileSync } = require('child_process');
9
+ const { ALL_HOSTS, HOST_LABELS, normalizeHosts } = require('./hosts');
10
+
11
+ /**
12
+ * @param {{ execFileSync?: Function }} [options]
13
+ * @returns {string}
14
+ */
15
+ function detectGithubLogin(options = {}) {
16
+ const exec = options.execFileSync || execFileSync;
17
+ try {
18
+ const out = exec('gh', ['api', 'user', '-q', '.login'], {
19
+ encoding: 'utf8',
20
+ stdio: ['ignore', 'pipe', 'pipe']
21
+ });
22
+ return String(out || '').trim();
23
+ } catch {
24
+ return '';
25
+ }
26
+ }
27
+
28
+ /**
29
+ * @param {string[]} argv
30
+ * @param {string} flag
31
+ * @returns {boolean}
32
+ */
33
+ function argvHasFlag(argv, flag) {
34
+ return argv.includes(flag) || argv.some((a) => a.startsWith(`${flag}=`));
35
+ }
36
+
37
+ /**
38
+ * Ask which AI coding hosts to install for (TTY only when --host not set).
39
+ * @param {object} options
40
+ * @param {{ interactive?: boolean, inquirer?: object, argv?: string[] }} [deps]
41
+ * @returns {Promise<object>}
42
+ */
43
+ async function enrichInitHostOptions(options, deps = {}) {
44
+ const interactive = Boolean(deps.interactive);
45
+ const argv = deps.argv || process.argv;
46
+ if (!interactive || argvHasFlag(argv, '--host')) {
47
+ return options;
48
+ }
49
+ const inquirer = deps.inquirer || require('inquirer');
50
+ const { host } = await inquirer.prompt([
51
+ {
52
+ type: 'list',
53
+ name: 'host',
54
+ message: 'Which AI coding platforms should Agent Kit support?',
55
+ default: 'all',
56
+ choices: [
57
+ {
58
+ name: `All (${ALL_HOSTS.map((h) => HOST_LABELS[h] || h).join(', ')})`,
59
+ value: 'all'
60
+ },
61
+ ...ALL_HOSTS.map((h) => ({
62
+ name: HOST_LABELS[h] || h,
63
+ value: h
64
+ }))
65
+ ]
66
+ }
67
+ ]);
68
+ return { ...options, host: host || 'all' };
69
+ }
70
+
71
+ /**
72
+ * @param {object} deps
73
+ * @returns {Promise<import('inquirer').Answers>}
74
+ */
75
+ async function askRemoteFields(deps) {
76
+ const inquirer = deps.inquirer || require('inquirer');
77
+ const defaultOrg = deps.defaultOrg || '';
78
+ return inquirer.prompt([
79
+ {
80
+ type: 'input',
81
+ name: 'org',
82
+ message: 'GitHub org or user:',
83
+ default: defaultOrg || undefined,
84
+ when: () => !deps.org,
85
+ validate: (v) => (String(v || '').trim() ? true : 'Org or user is required')
86
+ },
87
+ {
88
+ type: 'list',
89
+ name: 'visibility',
90
+ message: 'Repository visibility:',
91
+ choices: [
92
+ { name: 'private (recommended)', value: 'private' },
93
+ { name: 'public', value: 'public' }
94
+ ],
95
+ default: 'private',
96
+ when: () => !deps.visibilitySet
97
+ },
98
+ {
99
+ type: 'confirm',
100
+ name: 'confirmed',
101
+ message: (answers) => {
102
+ const org = deps.org || answers.org;
103
+ const vis = deps.visibility || answers.visibility || 'private';
104
+ const full = `${org}/${deps.name}`;
105
+ return `Create github:${full} (${vis}) and push?`;
106
+ },
107
+ default: false,
108
+ when: () => !deps.yes
109
+ }
110
+ ]);
111
+ }
112
+
113
+ /**
114
+ * Fill org/visibility and confirm on TTY. Non-interactive requires --org.
115
+ * @param {object} options
116
+ * @param {{ interactive?: boolean, name?: string, inquirer?: object, execFileSync?: Function, argv?: string[] }} [deps]
117
+ * @returns {Promise<object>}
118
+ */
119
+ async function enrichRemoteOptions(options, deps = {}) {
120
+ const interactive = Boolean(deps.interactive);
121
+ const name = deps.name || options.name || 'repo';
122
+ const argv = deps.argv || process.argv;
123
+ const visibilitySet = argvHasFlag(argv, '--visibility');
124
+ let org = options.org;
125
+ let visibility = options.visibility || 'private';
126
+
127
+ if (!interactive) {
128
+ if (!org) {
129
+ throw new Error('Non-interactive remote create requires --org <owner>.');
130
+ }
131
+ return { ...options, org, visibility, name };
132
+ }
133
+
134
+ const defaultOrg = org || detectGithubLogin({ execFileSync: deps.execFileSync });
135
+ const answers = await askRemoteFields({
136
+ inquirer: deps.inquirer,
137
+ org,
138
+ name,
139
+ visibility,
140
+ visibilitySet,
141
+ defaultOrg,
142
+ yes: Boolean(options.yes)
143
+ });
144
+ org = String(org || answers.org || '').trim();
145
+ if (answers.visibility) {
146
+ visibility = answers.visibility;
147
+ }
148
+ if (!org) {
149
+ throw new Error('Org or user is required.');
150
+ }
151
+ if (options.yes !== true && answers.confirmed === false) {
152
+ return { ...options, org, visibility, name, cancelled: true };
153
+ }
154
+ return { ...options, org, visibility, name, remote: true };
155
+ }
156
+
157
+ /**
158
+ * On TTY without --remote/--no-remote, ask whether to create a remote.
159
+ * @param {object} options
160
+ * @param {{ interactive?: boolean, inquirer?: object, execFileSync?: Function, workspaceName?: string }} [deps]
161
+ * @returns {Promise<object>}
162
+ */
163
+ async function enrichInitRemoteOptions(options, deps = {}) {
164
+ const interactive = Boolean(deps.interactive);
165
+ const remoteExplicit = options.remote === true;
166
+ const noRemote = options.noRemote === true || options.remote === false;
167
+
168
+ if (noRemote || !interactive) {
169
+ return options;
170
+ }
171
+
172
+ let wantRemote = remoteExplicit;
173
+ if (!remoteExplicit) {
174
+ const inquirer = deps.inquirer || require('inquirer');
175
+ const { createRemote } = await inquirer.prompt([
176
+ {
177
+ type: 'confirm',
178
+ name: 'createRemote',
179
+ message: 'Create a private GitHub remote and push now?',
180
+ default: false
181
+ }
182
+ ]);
183
+ wantRemote = Boolean(createRemote);
184
+ }
185
+ if (!wantRemote) {
186
+ return { ...options, remote: false };
187
+ }
188
+
189
+ return enrichRemoteOptions(
190
+ { ...options, remote: true },
191
+ {
192
+ interactive: true,
193
+ name: deps.workspaceName || options.nameOrPath,
194
+ inquirer: deps.inquirer,
195
+ execFileSync: deps.execFileSync
196
+ }
197
+ );
198
+ }
199
+
200
+ /**
201
+ * TTY asks; --approve-all / --yes / non-TTY auto-approve; --skip-approve-all skips.
202
+ * @param {object} options
203
+ * @param {{ interactive?: boolean, inquirer?: object, ask?: boolean }} [deps]
204
+ * @returns {Promise<object>}
205
+ */
206
+ async function enrichApproveAllOptions(options, deps = {}) {
207
+ const hosts = normalizeHosts(options.host);
208
+ const needsHost = hosts.includes('claude') || hosts.includes('codex');
209
+ if (!needsHost || options.skipApproveAll) {
210
+ return { ...options, approveAll: false };
211
+ }
212
+ if (options.approveAll || options.yes) {
213
+ return { ...options, approveAll: true };
214
+ }
215
+ if (deps.ask === false) {
216
+ return { ...options, approveAll: false };
217
+ }
218
+ if (!deps.interactive) {
219
+ return { ...options, approveAll: true };
220
+ }
221
+ const inquirer = deps.inquirer || require('inquirer');
222
+ const { approveAll } = await inquirer.prompt([
223
+ {
224
+ type: 'confirm',
225
+ name: 'approveAll',
226
+ message: 'Auto-approve all Claude Code and Codex tool permissions in this workspace?',
227
+ default: true
228
+ }
229
+ ]);
230
+ return { ...options, approveAll: Boolean(approveAll) };
231
+ }
232
+
233
+ module.exports = {
234
+ detectGithubLogin,
235
+ enrichInitHostOptions,
236
+ enrichRemoteOptions,
237
+ enrichInitRemoteOptions,
238
+ enrichApproveAllOptions
239
+ };
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Render Agent Kit markdown for the selected coding hosts.
3
+ * @fileoverview Handlebars host flags for AGENTKIT.md
4
+ */
5
+
6
+ 'use strict';
7
+
8
+ const Handlebars = require('handlebars');
9
+ const { ALL_HOSTS } = require('./hosts');
10
+
11
+ /**
12
+ * @param {string[]} hosts
13
+ * @returns {object}
14
+ */
15
+ function hostMarkdownContext(hosts) {
16
+ const selected = Array.isArray(hosts) && hosts.length ? hosts : ALL_HOSTS.slice();
17
+ const cursor = selected.includes('cursor');
18
+ const claude = selected.includes('claude');
19
+ const codex = selected.includes('codex');
20
+ const hostFlag = selected.length === ALL_HOSTS.length ? 'all' : selected.join(',');
21
+ return {
22
+ cursor,
23
+ claude,
24
+ codex,
25
+ autoApprove: claude || codex,
26
+ hostFlag
27
+ };
28
+ }
29
+
30
+ /**
31
+ * @param {string} source
32
+ * @param {string[]} hosts
33
+ * @returns {string}
34
+ */
35
+ function renderHostMarkdown(source, hosts) {
36
+ const template = Handlebars.compile(String(source || ''), { noEscape: true });
37
+ return template(hostMarkdownContext(hosts)).replace(/\n{3,}/g, '\n\n');
38
+ }
39
+
40
+ module.exports = { hostMarkdownContext, renderHostMarkdown };
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Git repository root guard for Agent Kit install.
3
+ * @fileoverview Refuse home, filesystem root, and non-git directories
4
+ */
5
+
6
+ 'use strict';
7
+
8
+ const os = require('os');
9
+ const path = require('path');
10
+ const { execFileSync } = require('child_process');
11
+
12
+ /**
13
+ * @param {string} resolved
14
+ * @returns {boolean}
15
+ */
16
+ function isUnsafeRoot(resolved) {
17
+ const home = path.resolve(os.homedir());
18
+ const root = path.resolve(resolved);
19
+ if (root === path.parse(root).root) {
20
+ return true;
21
+ }
22
+ return root === home;
23
+ }
24
+
25
+ /**
26
+ * @param {string} cwd
27
+ * @returns {string}
28
+ */
29
+ function resolveGitRoot(cwd) {
30
+ let output;
31
+ try {
32
+ output = execFileSync('git', ['rev-parse', '--show-toplevel'], {
33
+ cwd,
34
+ encoding: 'utf8',
35
+ stdio: ['ignore', 'pipe', 'pipe']
36
+ }).trim();
37
+ } catch {
38
+ throw new Error('Agent Kit commands must run inside a git repository.');
39
+ }
40
+ const resolved = path.resolve(output);
41
+ if (isUnsafeRoot(resolved)) {
42
+ throw new Error('Refusing to install the Agent Kit into a filesystem root or home directory.');
43
+ }
44
+ return resolved;
45
+ }
46
+
47
+ module.exports = { isUnsafeRoot, resolveGitRoot };
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Human and JSON reports for Agent Kit commands.
3
+ * @fileoverview Status envelope without ANSI in JSON
4
+ */
5
+
6
+ 'use strict';
7
+
8
+ const { ACTION, KIT_STATE } = require('./constants');
9
+ const { hostLabel } = require('./hosts');
10
+
11
+ /**
12
+ * @param {object[]} ops
13
+ * @returns {string[]}
14
+ */
15
+ function conflictPaths(ops) {
16
+ return ops.filter((op) => op.action === ACTION.CONFLICT).map((op) => op.targetRel);
17
+ }
18
+
19
+ /**
20
+ * @param {object[]} ops
21
+ * @returns {number}
22
+ */
23
+ function countInstalledSkills(ops) {
24
+ return (ops || []).filter((op) => {
25
+ const rel = op.targetRel || '';
26
+ const skillFile = rel.includes('.agents/skills/') && rel.endsWith('SKILL.md');
27
+ return skillFile && op.action !== ACTION.DELETE && op.action !== ACTION.CONFLICT;
28
+ }).length;
29
+ }
30
+
31
+ /**
32
+ * @param {object|null} state
33
+ * @param {object} kit
34
+ * @param {object[]} ops
35
+ * @returns {string}
36
+ */
37
+ function kitStateName(state, kit, ops) {
38
+ if (!state) {
39
+ return KIT_STATE.NOT_INSTALLED;
40
+ }
41
+ if (conflictPaths(ops).length > 0) {
42
+ return KIT_STATE.CONFLICTS;
43
+ }
44
+ if (state.kitVersion !== kit.manifest.kitVersion) {
45
+ return KIT_STATE.UPDATE_AVAILABLE;
46
+ }
47
+ const applied = new Set([ACTION.CREATE, ACTION.REPLACE, ACTION.APPEND_SECTION]);
48
+ const missing = ops.some((op) => {
49
+ return op.fileState === 'MISSING' && op.mode !== 'obsolete' && !applied.has(op.action);
50
+ });
51
+ return missing ? KIT_STATE.UPDATE_AVAILABLE : KIT_STATE.CURRENT;
52
+ }
53
+
54
+ /**
55
+ * @param {object} params
56
+ * @returns {object}
57
+ */
58
+ function buildJsonReport(params) {
59
+ const { command, kit, state, hosts, ops, dryRun, builderVersion, kitState, approvals } = params;
60
+ return {
61
+ success: conflictPaths(ops).length === 0,
62
+ command,
63
+ dryRun: Boolean(dryRun),
64
+ state: kitState,
65
+ kitVersion: kit.manifest.kitVersion,
66
+ installedKitVersion: state ? state.kitVersion : null,
67
+ builderVersion,
68
+ skills: {
69
+ expected: (kit.manifest.skills || []).length,
70
+ installed: countInstalledSkills(ops)
71
+ },
72
+ hosts,
73
+ files: (ops || []).map((op) => ({
74
+ path: op.targetRel,
75
+ classification: op.fileState,
76
+ action: op.action
77
+ })),
78
+ conflicts: conflictPaths(ops),
79
+ approvals: approvals || { requested: false, files: [] }
80
+ };
81
+ }
82
+
83
+ /**
84
+ * @param {object} json
85
+ * @returns {string}
86
+ */
87
+ function formatHumanStatus(json) {
88
+ const installed = json.installedKitVersion || '—';
89
+ const hosts = (json.hosts || []).map((h) => hostLabel(h)).join(', ');
90
+ const lines = [
91
+ 'AI Fabrix Agent Kit',
92
+ `Installed ${installed}`,
93
+ `Available ${json.kitVersion} (Builder ${json.builderVersion})`,
94
+ `Skills ${json.skills.installed}/${json.skills.expected}`,
95
+ `Hosts ${hosts || '—'}`,
96
+ `State ${json.state}`
97
+ ];
98
+ if (json.approvals && json.approvals.requested) {
99
+ const paths = (json.approvals.files || [])
100
+ .filter((row) => row.action !== 'skip')
101
+ .map((row) => `${row.path} (${row.action})`);
102
+ lines.push(`Approvals ${paths.join(', ') || 'none'}`);
103
+ }
104
+ return lines.join('\n');
105
+ }
106
+
107
+ module.exports = { conflictPaths, kitStateName, buildJsonReport, formatHumanStatus };
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Shared Agent Kit install/update runner.
3
+ * @fileoverview Classify, plan, optionally apply, write state
4
+ */
5
+
6
+ 'use strict';
7
+
8
+ const { getCliVersion } = require('../utils/installation-log-core');
9
+ const { resolveGitRoot } = require('./repo-guard');
10
+ const { loadKitPackage } = require('./package-load');
11
+ const { listManagedTargets } = require('./file-list');
12
+ const { classifyItems } = require('./classify');
13
+ const { detectV1, listObsoleteV1Items, markV1ShimReplacements } = require('./v1-detect');
14
+ const { loadState, buildStateDocument, writeState } = require('./state-file');
15
+ const { actionForInstall, actionForUpdate, assignActions } = require('./plan-ops');
16
+ const { applyOps } = require('./apply-ops');
17
+ const { resolveManagedHosts } = require('./hosts');
18
+ const { kitStateName, buildJsonReport } = require('./report');
19
+ const { writeApproveAllFiles } = require('./host-approve');
20
+
21
+ /**
22
+ * @param {object} options
23
+ * @param {'install'|'update'} mode
24
+ * @returns {object}
25
+ */
26
+ function runManagedCommand(options, mode) {
27
+ const repoRoot = resolveGitRoot(options.cwd || process.cwd());
28
+ const kit = loadKitPackage();
29
+ const state = loadState(repoRoot);
30
+ const hosts = resolveManagedHosts(options, state, mode);
31
+ const includeShims = detectV1(repoRoot) && hosts.includes('cursor');
32
+ const includeClaude = hosts.includes('claude');
33
+ const items = listManagedTargets(kit, repoRoot, { includeShims, includeClaude });
34
+ classifyItems(items, state, hosts);
35
+ markV1ShimReplacements(items, kit);
36
+ const managedRels = new Set(items.map((item) => item.targetRel));
37
+ items.push(...listObsoleteV1Items(kit, repoRoot, managedRels));
38
+ const picker = mode === 'update' ? actionForUpdate : actionForInstall;
39
+ const ops = assignActions(items, picker, Boolean(options.force));
40
+ if (!options.dryRun) {
41
+ applyOps(ops);
42
+ writeState(repoRoot, buildStateDocument(kit, hosts, ops, getCliVersion()));
43
+ }
44
+ const builderVersion = getCliVersion();
45
+ const kitState = kitStateName(options.dryRun ? state : loadState(repoRoot) || state, kit, ops);
46
+ const approvals = writeApproveAllFiles({
47
+ repoRoot,
48
+ hosts,
49
+ approveAll: Boolean(options.approveAll),
50
+ dryRun: Boolean(options.dryRun),
51
+ force: Boolean(options.force)
52
+ });
53
+ return buildJsonReport({
54
+ command: mode,
55
+ kit,
56
+ state: options.dryRun ? state : loadState(repoRoot) || state,
57
+ hosts,
58
+ ops,
59
+ dryRun: Boolean(options.dryRun),
60
+ builderVersion,
61
+ kitState,
62
+ approvals
63
+ });
64
+ }
65
+
66
+ /**
67
+ * @param {object} options
68
+ * @returns {object}
69
+ */
70
+ function runInstall(options) {
71
+ return runManagedCommand(options, 'install');
72
+ }
73
+
74
+ /**
75
+ * @param {object} options
76
+ * @returns {object}
77
+ */
78
+ function runUpdate(options) {
79
+ return runManagedCommand(options, 'update');
80
+ }
81
+
82
+ module.exports = { runInstall, runUpdate, runManagedCommand };
@@ -0,0 +1,122 @@
1
+ /**
2
+ * Local git SCM helpers for agent-kit init.
3
+ * @fileoverview git init / detect / initial commit
4
+ */
5
+
6
+ 'use strict';
7
+
8
+ const path = require('path');
9
+ const fs = require('../../internal/fs-real-sync');
10
+ const { execFileSync } = require('child_process');
11
+
12
+ const INITIAL_COMMIT_MESSAGE = 'Initial customer workspace with Agent Kit.';
13
+
14
+ /**
15
+ * @param {string} dir
16
+ * @returns {boolean}
17
+ */
18
+ function isGitRepository(dir) {
19
+ return fs.existsSync(path.join(dir, '.git'));
20
+ }
21
+
22
+ /**
23
+ * @param {string} dir
24
+ * @param {{ dryRun?: boolean, execFileSync?: Function }} [options]
25
+ * @returns {{ initialized: boolean, skipped: boolean }}
26
+ */
27
+ function ensureGitRepository(dir, options = {}) {
28
+ const exec = options.execFileSync || execFileSync;
29
+ if (isGitRepository(dir)) {
30
+ return { initialized: false, skipped: true };
31
+ }
32
+ if (options.dryRun) {
33
+ return { initialized: true, skipped: false };
34
+ }
35
+ exec('git', ['init', '-b', 'main'], { cwd: dir, stdio: 'ignore' });
36
+ return { initialized: true, skipped: false };
37
+ }
38
+
39
+ /**
40
+ * @param {string} dir
41
+ * @param {Function} exec
42
+ * @returns {boolean}
43
+ */
44
+ function hasCommits(dir, exec) {
45
+ try {
46
+ exec('git', ['rev-parse', 'HEAD'], { cwd: dir, stdio: 'ignore' });
47
+ return true;
48
+ } catch {
49
+ return false;
50
+ }
51
+ }
52
+
53
+ /**
54
+ * Create the first commit when HEAD is missing (required before gh --push).
55
+ * Uses one-shot -c identity; does not rewrite git config.
56
+ * @param {string} dir
57
+ * @param {{ dryRun?: boolean, execFileSync?: Function }} [options]
58
+ * @returns {{ committed: boolean, skipped: boolean, planned?: boolean }}
59
+ */
60
+ function ensureInitialCommit(dir, options = {}) {
61
+ const exec = options.execFileSync || execFileSync;
62
+ if (options.dryRun) {
63
+ return { committed: true, skipped: false, planned: true };
64
+ }
65
+ if (hasCommits(dir, exec)) {
66
+ return { committed: false, skipped: true };
67
+ }
68
+ exec('git', ['add', '-A'], { cwd: dir, stdio: 'ignore' });
69
+ const status = exec('git', ['status', '--porcelain'], {
70
+ cwd: dir,
71
+ encoding: 'utf8',
72
+ stdio: ['ignore', 'pipe', 'pipe']
73
+ });
74
+ if (!String(status || '').trim()) {
75
+ throw new Error('No files to commit before creating the remote.');
76
+ }
77
+ exec(
78
+ 'git',
79
+ [
80
+ '-c',
81
+ 'user.email=noreply@aifabrix.ai',
82
+ '-c',
83
+ 'user.name=AI Fabrix Builder',
84
+ 'commit',
85
+ '-m',
86
+ INITIAL_COMMIT_MESSAGE
87
+ ],
88
+ { cwd: dir, stdio: 'ignore' }
89
+ );
90
+ return { committed: true, skipped: false };
91
+ }
92
+
93
+ /**
94
+ * @param {string} dir
95
+ * @param {{ execFileSync?: Function }} [options]
96
+ * @returns {boolean}
97
+ */
98
+ function hasOriginRemote(dir, options = {}) {
99
+ const exec = options.execFileSync || execFileSync;
100
+ try {
101
+ const out = exec('git', ['remote'], {
102
+ cwd: dir,
103
+ encoding: 'utf8',
104
+ stdio: ['ignore', 'pipe', 'pipe']
105
+ });
106
+ return String(out)
107
+ .split(/\r?\n/)
108
+ .map((line) => line.trim())
109
+ .includes('origin');
110
+ } catch {
111
+ return false;
112
+ }
113
+ }
114
+
115
+ module.exports = {
116
+ isGitRepository,
117
+ ensureGitRepository,
118
+ ensureInitialCommit,
119
+ hasOriginRemote,
120
+ hasCommits,
121
+ INITIAL_COMMIT_MESSAGE
122
+ };