@microsoft/agentrc 2.0.1-1 → 2.0.1-10
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/README.md +54 -226
- package/dist/index.js +1095 -517
- package/dist/index.js.map +1 -1
- package/dist/skills/area-instructions/SKILL.md +37 -0
- package/dist/skills/nested-detail/SKILL.md +22 -0
- package/dist/skills/nested-hub/SKILL.md +44 -0
- package/dist/skills/root-instructions/SKILL.md +29 -0
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { createRequire as __bannerCreateRequire } from "node:module";
|
|
3
|
+
const require = __bannerCreateRequire(import.meta.url);
|
|
2
4
|
var __create = Object.create;
|
|
3
5
|
var __defProp = Object.defineProperty;
|
|
4
6
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
@@ -3050,7 +3052,7 @@ var require_main = __commonJS({
|
|
|
3050
3052
|
exports.createMessageConnection = exports.createServerSocketTransport = exports.createClientSocketTransport = exports.createServerPipeTransport = exports.createClientPipeTransport = exports.generateRandomPipeName = exports.StreamMessageWriter = exports.StreamMessageReader = exports.SocketMessageWriter = exports.SocketMessageReader = exports.PortMessageWriter = exports.PortMessageReader = exports.IPCMessageWriter = exports.IPCMessageReader = void 0;
|
|
3051
3053
|
var ril_1 = require_ril();
|
|
3052
3054
|
ril_1.default.install();
|
|
3053
|
-
var
|
|
3055
|
+
var path23 = __require("path");
|
|
3054
3056
|
var os2 = __require("os");
|
|
3055
3057
|
var crypto_1 = __require("crypto");
|
|
3056
3058
|
var net_1 = __require("net");
|
|
@@ -3186,9 +3188,9 @@ var require_main = __commonJS({
|
|
|
3186
3188
|
}
|
|
3187
3189
|
let result;
|
|
3188
3190
|
if (XDG_RUNTIME_DIR) {
|
|
3189
|
-
result =
|
|
3191
|
+
result = path23.join(XDG_RUNTIME_DIR, `vscode-ipc-${randomSuffix}.sock`);
|
|
3190
3192
|
} else {
|
|
3191
|
-
result =
|
|
3193
|
+
result = path23.join(os2.tmpdir(), `vscode-${randomSuffix}.sock`);
|
|
3192
3194
|
}
|
|
3193
3195
|
const limit = safeIpcPathLengths.get(process.platform);
|
|
3194
3196
|
if (limit !== void 0 && result.length > limit) {
|
|
@@ -3329,23 +3331,63 @@ function createSessionRpc(connection, sessionId) {
|
|
|
3329
3331
|
readFile: async (params) => connection.sendRequest("session.workspace.readFile", { sessionId, ...params }),
|
|
3330
3332
|
createFile: async (params) => connection.sendRequest("session.workspace.createFile", { sessionId, ...params })
|
|
3331
3333
|
},
|
|
3334
|
+
/** @experimental */
|
|
3332
3335
|
fleet: {
|
|
3333
3336
|
start: async (params) => connection.sendRequest("session.fleet.start", { sessionId, ...params })
|
|
3334
3337
|
},
|
|
3338
|
+
/** @experimental */
|
|
3335
3339
|
agent: {
|
|
3336
3340
|
list: async () => connection.sendRequest("session.agent.list", { sessionId }),
|
|
3337
3341
|
getCurrent: async () => connection.sendRequest("session.agent.getCurrent", { sessionId }),
|
|
3338
3342
|
select: async (params) => connection.sendRequest("session.agent.select", { sessionId, ...params }),
|
|
3339
|
-
deselect: async () => connection.sendRequest("session.agent.deselect", { sessionId })
|
|
3343
|
+
deselect: async () => connection.sendRequest("session.agent.deselect", { sessionId }),
|
|
3344
|
+
reload: async () => connection.sendRequest("session.agent.reload", { sessionId })
|
|
3340
3345
|
},
|
|
3346
|
+
/** @experimental */
|
|
3347
|
+
skills: {
|
|
3348
|
+
list: async () => connection.sendRequest("session.skills.list", { sessionId }),
|
|
3349
|
+
enable: async (params) => connection.sendRequest("session.skills.enable", { sessionId, ...params }),
|
|
3350
|
+
disable: async (params) => connection.sendRequest("session.skills.disable", { sessionId, ...params }),
|
|
3351
|
+
reload: async () => connection.sendRequest("session.skills.reload", { sessionId })
|
|
3352
|
+
},
|
|
3353
|
+
/** @experimental */
|
|
3354
|
+
mcp: {
|
|
3355
|
+
list: async () => connection.sendRequest("session.mcp.list", { sessionId }),
|
|
3356
|
+
enable: async (params) => connection.sendRequest("session.mcp.enable", { sessionId, ...params }),
|
|
3357
|
+
disable: async (params) => connection.sendRequest("session.mcp.disable", { sessionId, ...params }),
|
|
3358
|
+
reload: async () => connection.sendRequest("session.mcp.reload", { sessionId })
|
|
3359
|
+
},
|
|
3360
|
+
/** @experimental */
|
|
3361
|
+
plugins: {
|
|
3362
|
+
list: async () => connection.sendRequest("session.plugins.list", { sessionId })
|
|
3363
|
+
},
|
|
3364
|
+
/** @experimental */
|
|
3365
|
+
extensions: {
|
|
3366
|
+
list: async () => connection.sendRequest("session.extensions.list", { sessionId }),
|
|
3367
|
+
enable: async (params) => connection.sendRequest("session.extensions.enable", { sessionId, ...params }),
|
|
3368
|
+
disable: async (params) => connection.sendRequest("session.extensions.disable", { sessionId, ...params }),
|
|
3369
|
+
reload: async () => connection.sendRequest("session.extensions.reload", { sessionId })
|
|
3370
|
+
},
|
|
3371
|
+
/** @experimental */
|
|
3341
3372
|
compaction: {
|
|
3342
3373
|
compact: async () => connection.sendRequest("session.compaction.compact", { sessionId })
|
|
3343
3374
|
},
|
|
3344
3375
|
tools: {
|
|
3345
3376
|
handlePendingToolCall: async (params) => connection.sendRequest("session.tools.handlePendingToolCall", { sessionId, ...params })
|
|
3346
3377
|
},
|
|
3378
|
+
commands: {
|
|
3379
|
+
handlePendingCommand: async (params) => connection.sendRequest("session.commands.handlePendingCommand", { sessionId, ...params })
|
|
3380
|
+
},
|
|
3381
|
+
ui: {
|
|
3382
|
+
elicitation: async (params) => connection.sendRequest("session.ui.elicitation", { sessionId, ...params })
|
|
3383
|
+
},
|
|
3347
3384
|
permissions: {
|
|
3348
3385
|
handlePendingPermissionRequest: async (params) => connection.sendRequest("session.permissions.handlePendingPermissionRequest", { sessionId, ...params })
|
|
3386
|
+
},
|
|
3387
|
+
log: async (params) => connection.sendRequest("session.log", { sessionId, ...params }),
|
|
3388
|
+
shell: {
|
|
3389
|
+
exec: async (params) => connection.sendRequest("session.shell.exec", { sessionId, ...params }),
|
|
3390
|
+
kill: async (params) => connection.sendRequest("session.shell.kill", { sessionId, ...params })
|
|
3349
3391
|
}
|
|
3350
3392
|
};
|
|
3351
3393
|
}
|
|
@@ -3367,13 +3409,30 @@ var init_sdkProtocolVersion = __esm({
|
|
|
3367
3409
|
}
|
|
3368
3410
|
});
|
|
3369
3411
|
|
|
3412
|
+
// node_modules/@github/copilot-sdk/dist/telemetry.js
|
|
3413
|
+
async function getTraceContext(provider) {
|
|
3414
|
+
if (!provider) return {};
|
|
3415
|
+
try {
|
|
3416
|
+
return await provider() ?? {};
|
|
3417
|
+
} catch {
|
|
3418
|
+
return {};
|
|
3419
|
+
}
|
|
3420
|
+
}
|
|
3421
|
+
var init_telemetry = __esm({
|
|
3422
|
+
"node_modules/@github/copilot-sdk/dist/telemetry.js"() {
|
|
3423
|
+
"use strict";
|
|
3424
|
+
}
|
|
3425
|
+
});
|
|
3426
|
+
|
|
3370
3427
|
// node_modules/@github/copilot-sdk/dist/session.js
|
|
3371
|
-
var import_node, CopilotSession;
|
|
3428
|
+
var import_node, NO_RESULT_PERMISSION_V2_ERROR, CopilotSession;
|
|
3372
3429
|
var init_session = __esm({
|
|
3373
3430
|
"node_modules/@github/copilot-sdk/dist/session.js"() {
|
|
3374
3431
|
"use strict";
|
|
3375
3432
|
import_node = __toESM(require_node(), 1);
|
|
3376
3433
|
init_rpc();
|
|
3434
|
+
init_telemetry();
|
|
3435
|
+
NO_RESULT_PERMISSION_V2_ERROR = "Permission handlers cannot return 'no-result' when connected to a protocol v2 server.";
|
|
3377
3436
|
CopilotSession = class {
|
|
3378
3437
|
/**
|
|
3379
3438
|
* Creates a new CopilotSession instance.
|
|
@@ -3381,12 +3440,14 @@ var init_session = __esm({
|
|
|
3381
3440
|
* @param sessionId - The unique identifier for this session
|
|
3382
3441
|
* @param connection - The JSON-RPC message connection to the Copilot CLI
|
|
3383
3442
|
* @param workspacePath - Path to the session workspace directory (when infinite sessions enabled)
|
|
3443
|
+
* @param traceContextProvider - Optional callback to get W3C Trace Context for outbound RPCs
|
|
3384
3444
|
* @internal This constructor is internal. Use {@link CopilotClient.createSession} to create sessions.
|
|
3385
3445
|
*/
|
|
3386
|
-
constructor(sessionId, connection, _workspacePath) {
|
|
3446
|
+
constructor(sessionId, connection, _workspacePath, traceContextProvider) {
|
|
3387
3447
|
this.sessionId = sessionId;
|
|
3388
3448
|
this.connection = connection;
|
|
3389
3449
|
this._workspacePath = _workspacePath;
|
|
3450
|
+
this.traceContextProvider = traceContextProvider;
|
|
3390
3451
|
}
|
|
3391
3452
|
eventHandlers = /* @__PURE__ */ new Set();
|
|
3392
3453
|
typedEventHandlers = /* @__PURE__ */ new Map();
|
|
@@ -3394,7 +3455,9 @@ var init_session = __esm({
|
|
|
3394
3455
|
permissionHandler;
|
|
3395
3456
|
userInputHandler;
|
|
3396
3457
|
hooks;
|
|
3458
|
+
transformCallbacks;
|
|
3397
3459
|
_rpc = null;
|
|
3460
|
+
traceContextProvider;
|
|
3398
3461
|
/**
|
|
3399
3462
|
* Typed session-scoped RPC methods.
|
|
3400
3463
|
*/
|
|
@@ -3432,6 +3495,7 @@ var init_session = __esm({
|
|
|
3432
3495
|
*/
|
|
3433
3496
|
async send(options) {
|
|
3434
3497
|
const response = await this.connection.sendRequest("session.send", {
|
|
3498
|
+
...await getTraceContext(this.traceContextProvider),
|
|
3435
3499
|
sessionId: this.sessionId,
|
|
3436
3500
|
prompt: options.prompt,
|
|
3437
3501
|
attachments: options.attachments,
|
|
@@ -3561,9 +3625,19 @@ var init_session = __esm({
|
|
|
3561
3625
|
const { requestId, toolName } = event.data;
|
|
3562
3626
|
const args2 = event.data.arguments;
|
|
3563
3627
|
const toolCallId = event.data.toolCallId;
|
|
3628
|
+
const traceparent = event.data.traceparent;
|
|
3629
|
+
const tracestate = event.data.tracestate;
|
|
3564
3630
|
const handler = this.toolHandlers.get(toolName);
|
|
3565
3631
|
if (handler) {
|
|
3566
|
-
void this._executeToolAndRespond(
|
|
3632
|
+
void this._executeToolAndRespond(
|
|
3633
|
+
requestId,
|
|
3634
|
+
toolName,
|
|
3635
|
+
toolCallId,
|
|
3636
|
+
args2,
|
|
3637
|
+
handler,
|
|
3638
|
+
traceparent,
|
|
3639
|
+
tracestate
|
|
3640
|
+
);
|
|
3567
3641
|
}
|
|
3568
3642
|
} else if (event.type === "permission.requested") {
|
|
3569
3643
|
const { requestId, permissionRequest } = event.data;
|
|
@@ -3576,13 +3650,15 @@ var init_session = __esm({
|
|
|
3576
3650
|
* Executes a tool handler and sends the result back via RPC.
|
|
3577
3651
|
* @internal
|
|
3578
3652
|
*/
|
|
3579
|
-
async _executeToolAndRespond(requestId, toolName, toolCallId, args2, handler) {
|
|
3653
|
+
async _executeToolAndRespond(requestId, toolName, toolCallId, args2, handler, traceparent, tracestate) {
|
|
3580
3654
|
try {
|
|
3581
3655
|
const rawResult = await handler(args2, {
|
|
3582
3656
|
sessionId: this.sessionId,
|
|
3583
3657
|
toolCallId,
|
|
3584
3658
|
toolName,
|
|
3585
|
-
arguments: args2
|
|
3659
|
+
arguments: args2,
|
|
3660
|
+
traceparent,
|
|
3661
|
+
tracestate
|
|
3586
3662
|
});
|
|
3587
3663
|
let result;
|
|
3588
3664
|
if (rawResult == null) {
|
|
@@ -3613,6 +3689,9 @@ var init_session = __esm({
|
|
|
3613
3689
|
const result = await this.permissionHandler(permissionRequest, {
|
|
3614
3690
|
sessionId: this.sessionId
|
|
3615
3691
|
});
|
|
3692
|
+
if (result.kind === "no-result") {
|
|
3693
|
+
return;
|
|
3694
|
+
}
|
|
3616
3695
|
await this.rpc.permissions.handlePendingPermissionRequest({ requestId, result });
|
|
3617
3696
|
} catch (_error) {
|
|
3618
3697
|
try {
|
|
@@ -3693,6 +3772,40 @@ var init_session = __esm({
|
|
|
3693
3772
|
registerHooks(hooks) {
|
|
3694
3773
|
this.hooks = hooks;
|
|
3695
3774
|
}
|
|
3775
|
+
/**
|
|
3776
|
+
* Registers transform callbacks for system message sections.
|
|
3777
|
+
*
|
|
3778
|
+
* @param callbacks - Map of section ID to transform callback, or undefined to clear
|
|
3779
|
+
* @internal This method is typically called internally when creating a session.
|
|
3780
|
+
*/
|
|
3781
|
+
registerTransformCallbacks(callbacks) {
|
|
3782
|
+
this.transformCallbacks = callbacks;
|
|
3783
|
+
}
|
|
3784
|
+
/**
|
|
3785
|
+
* Handles a systemMessage.transform request from the runtime.
|
|
3786
|
+
* Dispatches each section to its registered transform callback.
|
|
3787
|
+
*
|
|
3788
|
+
* @param sections - Map of section IDs to their current rendered content
|
|
3789
|
+
* @returns A promise that resolves with the transformed sections
|
|
3790
|
+
* @internal This method is for internal use by the SDK.
|
|
3791
|
+
*/
|
|
3792
|
+
async _handleSystemMessageTransform(sections) {
|
|
3793
|
+
const result = {};
|
|
3794
|
+
for (const [sectionId, { content }] of Object.entries(sections)) {
|
|
3795
|
+
const callback = this.transformCallbacks?.get(sectionId);
|
|
3796
|
+
if (callback) {
|
|
3797
|
+
try {
|
|
3798
|
+
const transformed = await callback(content);
|
|
3799
|
+
result[sectionId] = { content: transformed };
|
|
3800
|
+
} catch (_error) {
|
|
3801
|
+
result[sectionId] = { content };
|
|
3802
|
+
}
|
|
3803
|
+
} else {
|
|
3804
|
+
result[sectionId] = { content };
|
|
3805
|
+
}
|
|
3806
|
+
}
|
|
3807
|
+
return { sections: result };
|
|
3808
|
+
}
|
|
3696
3809
|
/**
|
|
3697
3810
|
* Handles a permission request in the v2 protocol format (synchronous RPC).
|
|
3698
3811
|
* Used as a back-compat adapter when connected to a v2 server.
|
|
@@ -3709,8 +3822,14 @@ var init_session = __esm({
|
|
|
3709
3822
|
const result = await this.permissionHandler(request, {
|
|
3710
3823
|
sessionId: this.sessionId
|
|
3711
3824
|
});
|
|
3825
|
+
if (result.kind === "no-result") {
|
|
3826
|
+
throw new Error(NO_RESULT_PERMISSION_V2_ERROR);
|
|
3827
|
+
}
|
|
3712
3828
|
return result;
|
|
3713
|
-
} catch (
|
|
3829
|
+
} catch (error) {
|
|
3830
|
+
if (error instanceof Error && error.message === NO_RESULT_PERMISSION_V2_ERROR) {
|
|
3831
|
+
throw error;
|
|
3832
|
+
}
|
|
3714
3833
|
return { kind: "denied-no-approval-rule-and-could-not-request-from-user" };
|
|
3715
3834
|
}
|
|
3716
3835
|
}
|
|
@@ -3866,14 +3985,35 @@ var init_session = __esm({
|
|
|
3866
3985
|
* The new model takes effect for the next message. Conversation history is preserved.
|
|
3867
3986
|
*
|
|
3868
3987
|
* @param model - Model ID to switch to
|
|
3988
|
+
* @param options - Optional settings for the new model
|
|
3869
3989
|
*
|
|
3870
3990
|
* @example
|
|
3871
3991
|
* ```typescript
|
|
3872
3992
|
* await session.setModel("gpt-4.1");
|
|
3993
|
+
* await session.setModel("claude-sonnet-4.6", { reasoningEffort: "high" });
|
|
3873
3994
|
* ```
|
|
3874
3995
|
*/
|
|
3875
|
-
async setModel(model) {
|
|
3876
|
-
await this.rpc.model.switchTo({ modelId: model });
|
|
3996
|
+
async setModel(model, options) {
|
|
3997
|
+
await this.rpc.model.switchTo({ modelId: model, ...options });
|
|
3998
|
+
}
|
|
3999
|
+
/**
|
|
4000
|
+
* Log a message to the session timeline.
|
|
4001
|
+
* The message appears in the session event stream and is visible to SDK consumers
|
|
4002
|
+
* and (for non-ephemeral messages) persisted to the session event log on disk.
|
|
4003
|
+
*
|
|
4004
|
+
* @param message - Human-readable message text
|
|
4005
|
+
* @param options - Optional log level and ephemeral flag
|
|
4006
|
+
*
|
|
4007
|
+
* @example
|
|
4008
|
+
* ```typescript
|
|
4009
|
+
* await session.log("Processing started");
|
|
4010
|
+
* await session.log("Disk usage high", { level: "warning" });
|
|
4011
|
+
* await session.log("Connection failed", { level: "error" });
|
|
4012
|
+
* await session.log("Debug info", { ephemeral: true });
|
|
4013
|
+
* ```
|
|
4014
|
+
*/
|
|
4015
|
+
async log(message, options) {
|
|
4016
|
+
await this.rpc.log({ message, ...options });
|
|
3877
4017
|
}
|
|
3878
4018
|
};
|
|
3879
4019
|
}
|
|
@@ -3881,7 +4021,9 @@ var init_session = __esm({
|
|
|
3881
4021
|
|
|
3882
4022
|
// node_modules/@github/copilot-sdk/dist/client.js
|
|
3883
4023
|
import { spawn } from "child_process";
|
|
4024
|
+
import { randomUUID } from "crypto";
|
|
3884
4025
|
import { existsSync } from "fs";
|
|
4026
|
+
import { createRequire } from "module";
|
|
3885
4027
|
import { Socket } from "net";
|
|
3886
4028
|
import { dirname, join } from "path";
|
|
3887
4029
|
import { fileURLToPath } from "url";
|
|
@@ -3895,6 +4037,30 @@ function toJsonSchema(parameters) {
|
|
|
3895
4037
|
}
|
|
3896
4038
|
return parameters;
|
|
3897
4039
|
}
|
|
4040
|
+
function extractTransformCallbacks(systemMessage) {
|
|
4041
|
+
if (!systemMessage || systemMessage.mode !== "customize" || !systemMessage.sections) {
|
|
4042
|
+
return { wirePayload: systemMessage, transformCallbacks: void 0 };
|
|
4043
|
+
}
|
|
4044
|
+
const transformCallbacks = /* @__PURE__ */ new Map();
|
|
4045
|
+
const wireSections = {};
|
|
4046
|
+
for (const [sectionId, override] of Object.entries(systemMessage.sections)) {
|
|
4047
|
+
if (!override) continue;
|
|
4048
|
+
if (typeof override.action === "function") {
|
|
4049
|
+
transformCallbacks.set(sectionId, override.action);
|
|
4050
|
+
wireSections[sectionId] = { action: "transform" };
|
|
4051
|
+
} else {
|
|
4052
|
+
wireSections[sectionId] = { action: override.action, content: override.content };
|
|
4053
|
+
}
|
|
4054
|
+
}
|
|
4055
|
+
if (transformCallbacks.size === 0) {
|
|
4056
|
+
return { wirePayload: systemMessage, transformCallbacks: void 0 };
|
|
4057
|
+
}
|
|
4058
|
+
const wirePayload = {
|
|
4059
|
+
...systemMessage,
|
|
4060
|
+
sections: wireSections
|
|
4061
|
+
};
|
|
4062
|
+
return { wirePayload, transformCallbacks };
|
|
4063
|
+
}
|
|
3898
4064
|
function getNodeExecPath() {
|
|
3899
4065
|
if (process.versions.bun) {
|
|
3900
4066
|
return "node";
|
|
@@ -3912,6 +4078,7 @@ var init_client = __esm({
|
|
|
3912
4078
|
init_rpc();
|
|
3913
4079
|
init_sdkProtocolVersion();
|
|
3914
4080
|
init_session();
|
|
4081
|
+
init_telemetry();
|
|
3915
4082
|
MIN_PROTOCOL_VERSION = 2;
|
|
3916
4083
|
CopilotClient = class {
|
|
3917
4084
|
cliProcess = null;
|
|
@@ -3926,6 +4093,8 @@ var init_client = __esm({
|
|
|
3926
4093
|
options;
|
|
3927
4094
|
isExternalServer = false;
|
|
3928
4095
|
forceStopping = false;
|
|
4096
|
+
onListModels;
|
|
4097
|
+
onGetTraceContext;
|
|
3929
4098
|
modelsCache = null;
|
|
3930
4099
|
modelsCacheLock = Promise.resolve();
|
|
3931
4100
|
sessionLifecycleHandlers = /* @__PURE__ */ new Set();
|
|
@@ -3991,8 +4160,10 @@ var init_client = __esm({
|
|
|
3991
4160
|
if (options.isChildProcess) {
|
|
3992
4161
|
this.isExternalServer = true;
|
|
3993
4162
|
}
|
|
4163
|
+
this.onListModels = options.onListModels;
|
|
4164
|
+
this.onGetTraceContext = options.onGetTraceContext;
|
|
3994
4165
|
this.options = {
|
|
3995
|
-
cliPath: options.cliPath || getBundledCliPath(),
|
|
4166
|
+
cliPath: options.cliUrl ? void 0 : options.cliPath || getBundledCliPath(),
|
|
3996
4167
|
cliArgs: options.cliArgs ?? [],
|
|
3997
4168
|
cwd: options.cwd ?? process.cwd(),
|
|
3998
4169
|
port: options.port || 0,
|
|
@@ -4002,11 +4173,12 @@ var init_client = __esm({
|
|
|
4002
4173
|
cliUrl: options.cliUrl,
|
|
4003
4174
|
logLevel: options.logLevel || "debug",
|
|
4004
4175
|
autoStart: options.autoStart ?? true,
|
|
4005
|
-
autoRestart:
|
|
4176
|
+
autoRestart: false,
|
|
4006
4177
|
env: options.env ?? process.env,
|
|
4007
4178
|
githubToken: options.githubToken,
|
|
4008
4179
|
// Default useLoggedInUser to false when githubToken is provided, otherwise true
|
|
4009
|
-
useLoggedInUser: options.useLoggedInUser ?? (options.githubToken ? false : true)
|
|
4180
|
+
useLoggedInUser: options.useLoggedInUser ?? (options.githubToken ? false : true),
|
|
4181
|
+
telemetry: options.telemetry
|
|
4010
4182
|
};
|
|
4011
4183
|
}
|
|
4012
4184
|
/**
|
|
@@ -4258,36 +4430,13 @@ var init_client = __esm({
|
|
|
4258
4430
|
throw new Error("Client not connected. Call start() first.");
|
|
4259
4431
|
}
|
|
4260
4432
|
}
|
|
4261
|
-
const
|
|
4262
|
-
|
|
4263
|
-
sessionId
|
|
4264
|
-
|
|
4265
|
-
|
|
4266
|
-
|
|
4267
|
-
|
|
4268
|
-
description: tool.description,
|
|
4269
|
-
parameters: toJsonSchema(tool.parameters),
|
|
4270
|
-
overridesBuiltInTool: tool.overridesBuiltInTool
|
|
4271
|
-
})),
|
|
4272
|
-
systemMessage: config.systemMessage,
|
|
4273
|
-
availableTools: config.availableTools,
|
|
4274
|
-
excludedTools: config.excludedTools,
|
|
4275
|
-
provider: config.provider,
|
|
4276
|
-
requestPermission: true,
|
|
4277
|
-
requestUserInput: !!config.onUserInputRequest,
|
|
4278
|
-
hooks: !!(config.hooks && Object.values(config.hooks).some(Boolean)),
|
|
4279
|
-
workingDirectory: config.workingDirectory,
|
|
4280
|
-
streaming: config.streaming,
|
|
4281
|
-
mcpServers: config.mcpServers,
|
|
4282
|
-
envValueMode: "direct",
|
|
4283
|
-
customAgents: config.customAgents,
|
|
4284
|
-
configDir: config.configDir,
|
|
4285
|
-
skillDirectories: config.skillDirectories,
|
|
4286
|
-
disabledSkills: config.disabledSkills,
|
|
4287
|
-
infiniteSessions: config.infiniteSessions
|
|
4288
|
-
});
|
|
4289
|
-
const { sessionId, workspacePath } = response;
|
|
4290
|
-
const session = new CopilotSession(sessionId, this.connection, workspacePath);
|
|
4433
|
+
const sessionId = config.sessionId ?? randomUUID();
|
|
4434
|
+
const session = new CopilotSession(
|
|
4435
|
+
sessionId,
|
|
4436
|
+
this.connection,
|
|
4437
|
+
void 0,
|
|
4438
|
+
this.onGetTraceContext
|
|
4439
|
+
);
|
|
4291
4440
|
session.registerTools(config.tools);
|
|
4292
4441
|
session.registerPermissionHandler(config.onPermissionRequest);
|
|
4293
4442
|
if (config.onUserInputRequest) {
|
|
@@ -4296,7 +4445,54 @@ var init_client = __esm({
|
|
|
4296
4445
|
if (config.hooks) {
|
|
4297
4446
|
session.registerHooks(config.hooks);
|
|
4298
4447
|
}
|
|
4448
|
+
const { wirePayload: wireSystemMessage, transformCallbacks } = extractTransformCallbacks(
|
|
4449
|
+
config.systemMessage
|
|
4450
|
+
);
|
|
4451
|
+
if (transformCallbacks) {
|
|
4452
|
+
session.registerTransformCallbacks(transformCallbacks);
|
|
4453
|
+
}
|
|
4454
|
+
if (config.onEvent) {
|
|
4455
|
+
session.on(config.onEvent);
|
|
4456
|
+
}
|
|
4299
4457
|
this.sessions.set(sessionId, session);
|
|
4458
|
+
try {
|
|
4459
|
+
const response = await this.connection.sendRequest("session.create", {
|
|
4460
|
+
...await getTraceContext(this.onGetTraceContext),
|
|
4461
|
+
model: config.model,
|
|
4462
|
+
sessionId,
|
|
4463
|
+
clientName: config.clientName,
|
|
4464
|
+
reasoningEffort: config.reasoningEffort,
|
|
4465
|
+
tools: config.tools?.map((tool) => ({
|
|
4466
|
+
name: tool.name,
|
|
4467
|
+
description: tool.description,
|
|
4468
|
+
parameters: toJsonSchema(tool.parameters),
|
|
4469
|
+
overridesBuiltInTool: tool.overridesBuiltInTool,
|
|
4470
|
+
skipPermission: tool.skipPermission
|
|
4471
|
+
})),
|
|
4472
|
+
systemMessage: wireSystemMessage,
|
|
4473
|
+
availableTools: config.availableTools,
|
|
4474
|
+
excludedTools: config.excludedTools,
|
|
4475
|
+
provider: config.provider,
|
|
4476
|
+
requestPermission: true,
|
|
4477
|
+
requestUserInput: !!config.onUserInputRequest,
|
|
4478
|
+
hooks: !!(config.hooks && Object.values(config.hooks).some(Boolean)),
|
|
4479
|
+
workingDirectory: config.workingDirectory,
|
|
4480
|
+
streaming: config.streaming,
|
|
4481
|
+
mcpServers: config.mcpServers,
|
|
4482
|
+
envValueMode: "direct",
|
|
4483
|
+
customAgents: config.customAgents,
|
|
4484
|
+
agent: config.agent,
|
|
4485
|
+
configDir: config.configDir,
|
|
4486
|
+
skillDirectories: config.skillDirectories,
|
|
4487
|
+
disabledSkills: config.disabledSkills,
|
|
4488
|
+
infiniteSessions: config.infiniteSessions
|
|
4489
|
+
});
|
|
4490
|
+
const { workspacePath } = response;
|
|
4491
|
+
session["_workspacePath"] = workspacePath;
|
|
4492
|
+
} catch (e) {
|
|
4493
|
+
this.sessions.delete(sessionId);
|
|
4494
|
+
throw e;
|
|
4495
|
+
}
|
|
4300
4496
|
return session;
|
|
4301
4497
|
}
|
|
4302
4498
|
/**
|
|
@@ -4336,37 +4532,12 @@ var init_client = __esm({
|
|
|
4336
4532
|
throw new Error("Client not connected. Call start() first.");
|
|
4337
4533
|
}
|
|
4338
4534
|
}
|
|
4339
|
-
const
|
|
4535
|
+
const session = new CopilotSession(
|
|
4340
4536
|
sessionId,
|
|
4341
|
-
|
|
4342
|
-
|
|
4343
|
-
|
|
4344
|
-
|
|
4345
|
-
availableTools: config.availableTools,
|
|
4346
|
-
excludedTools: config.excludedTools,
|
|
4347
|
-
tools: config.tools?.map((tool) => ({
|
|
4348
|
-
name: tool.name,
|
|
4349
|
-
description: tool.description,
|
|
4350
|
-
parameters: toJsonSchema(tool.parameters),
|
|
4351
|
-
overridesBuiltInTool: tool.overridesBuiltInTool
|
|
4352
|
-
})),
|
|
4353
|
-
provider: config.provider,
|
|
4354
|
-
requestPermission: true,
|
|
4355
|
-
requestUserInput: !!config.onUserInputRequest,
|
|
4356
|
-
hooks: !!(config.hooks && Object.values(config.hooks).some(Boolean)),
|
|
4357
|
-
workingDirectory: config.workingDirectory,
|
|
4358
|
-
configDir: config.configDir,
|
|
4359
|
-
streaming: config.streaming,
|
|
4360
|
-
mcpServers: config.mcpServers,
|
|
4361
|
-
envValueMode: "direct",
|
|
4362
|
-
customAgents: config.customAgents,
|
|
4363
|
-
skillDirectories: config.skillDirectories,
|
|
4364
|
-
disabledSkills: config.disabledSkills,
|
|
4365
|
-
infiniteSessions: config.infiniteSessions,
|
|
4366
|
-
disableResume: config.disableResume
|
|
4367
|
-
});
|
|
4368
|
-
const { sessionId: resumedSessionId, workspacePath } = response;
|
|
4369
|
-
const session = new CopilotSession(resumedSessionId, this.connection, workspacePath);
|
|
4537
|
+
this.connection,
|
|
4538
|
+
void 0,
|
|
4539
|
+
this.onGetTraceContext
|
|
4540
|
+
);
|
|
4370
4541
|
session.registerTools(config.tools);
|
|
4371
4542
|
session.registerPermissionHandler(config.onPermissionRequest);
|
|
4372
4543
|
if (config.onUserInputRequest) {
|
|
@@ -4375,7 +4546,55 @@ var init_client = __esm({
|
|
|
4375
4546
|
if (config.hooks) {
|
|
4376
4547
|
session.registerHooks(config.hooks);
|
|
4377
4548
|
}
|
|
4378
|
-
|
|
4549
|
+
const { wirePayload: wireSystemMessage, transformCallbacks } = extractTransformCallbacks(
|
|
4550
|
+
config.systemMessage
|
|
4551
|
+
);
|
|
4552
|
+
if (transformCallbacks) {
|
|
4553
|
+
session.registerTransformCallbacks(transformCallbacks);
|
|
4554
|
+
}
|
|
4555
|
+
if (config.onEvent) {
|
|
4556
|
+
session.on(config.onEvent);
|
|
4557
|
+
}
|
|
4558
|
+
this.sessions.set(sessionId, session);
|
|
4559
|
+
try {
|
|
4560
|
+
const response = await this.connection.sendRequest("session.resume", {
|
|
4561
|
+
...await getTraceContext(this.onGetTraceContext),
|
|
4562
|
+
sessionId,
|
|
4563
|
+
clientName: config.clientName,
|
|
4564
|
+
model: config.model,
|
|
4565
|
+
reasoningEffort: config.reasoningEffort,
|
|
4566
|
+
systemMessage: wireSystemMessage,
|
|
4567
|
+
availableTools: config.availableTools,
|
|
4568
|
+
excludedTools: config.excludedTools,
|
|
4569
|
+
tools: config.tools?.map((tool) => ({
|
|
4570
|
+
name: tool.name,
|
|
4571
|
+
description: tool.description,
|
|
4572
|
+
parameters: toJsonSchema(tool.parameters),
|
|
4573
|
+
overridesBuiltInTool: tool.overridesBuiltInTool,
|
|
4574
|
+
skipPermission: tool.skipPermission
|
|
4575
|
+
})),
|
|
4576
|
+
provider: config.provider,
|
|
4577
|
+
requestPermission: true,
|
|
4578
|
+
requestUserInput: !!config.onUserInputRequest,
|
|
4579
|
+
hooks: !!(config.hooks && Object.values(config.hooks).some(Boolean)),
|
|
4580
|
+
workingDirectory: config.workingDirectory,
|
|
4581
|
+
configDir: config.configDir,
|
|
4582
|
+
streaming: config.streaming,
|
|
4583
|
+
mcpServers: config.mcpServers,
|
|
4584
|
+
envValueMode: "direct",
|
|
4585
|
+
customAgents: config.customAgents,
|
|
4586
|
+
agent: config.agent,
|
|
4587
|
+
skillDirectories: config.skillDirectories,
|
|
4588
|
+
disabledSkills: config.disabledSkills,
|
|
4589
|
+
infiniteSessions: config.infiniteSessions,
|
|
4590
|
+
disableResume: config.disableResume
|
|
4591
|
+
});
|
|
4592
|
+
const { workspacePath } = response;
|
|
4593
|
+
session["_workspacePath"] = workspacePath;
|
|
4594
|
+
} catch (e) {
|
|
4595
|
+
this.sessions.delete(sessionId);
|
|
4596
|
+
throw e;
|
|
4597
|
+
}
|
|
4379
4598
|
return session;
|
|
4380
4599
|
}
|
|
4381
4600
|
/**
|
|
@@ -4436,15 +4655,15 @@ var init_client = __esm({
|
|
|
4436
4655
|
/**
|
|
4437
4656
|
* List available models with their metadata.
|
|
4438
4657
|
*
|
|
4658
|
+
* If an `onListModels` handler was provided in the client options,
|
|
4659
|
+
* it is called instead of querying the CLI server.
|
|
4660
|
+
*
|
|
4439
4661
|
* Results are cached after the first successful call to avoid rate limiting.
|
|
4440
4662
|
* The cache is cleared when the client disconnects.
|
|
4441
4663
|
*
|
|
4442
|
-
* @throws Error if not
|
|
4664
|
+
* @throws Error if not connected (when no custom handler is set)
|
|
4443
4665
|
*/
|
|
4444
4666
|
async listModels() {
|
|
4445
|
-
if (!this.connection) {
|
|
4446
|
-
throw new Error("Client not connected");
|
|
4447
|
-
}
|
|
4448
4667
|
await this.modelsCacheLock;
|
|
4449
4668
|
let resolveLock;
|
|
4450
4669
|
this.modelsCacheLock = new Promise((resolve) => {
|
|
@@ -4454,10 +4673,18 @@ var init_client = __esm({
|
|
|
4454
4673
|
if (this.modelsCache !== null) {
|
|
4455
4674
|
return [...this.modelsCache];
|
|
4456
4675
|
}
|
|
4457
|
-
|
|
4458
|
-
|
|
4459
|
-
|
|
4460
|
-
|
|
4676
|
+
let models;
|
|
4677
|
+
if (this.onListModels) {
|
|
4678
|
+
models = await this.onListModels();
|
|
4679
|
+
} else {
|
|
4680
|
+
if (!this.connection) {
|
|
4681
|
+
throw new Error("Client not connected");
|
|
4682
|
+
}
|
|
4683
|
+
const result = await this.connection.sendRequest("models.list", {});
|
|
4684
|
+
const response = result;
|
|
4685
|
+
models = response.models;
|
|
4686
|
+
}
|
|
4687
|
+
this.modelsCache = [...models];
|
|
4461
4688
|
return [...models];
|
|
4462
4689
|
} finally {
|
|
4463
4690
|
resolveLock();
|
|
@@ -4670,6 +4897,27 @@ var init_client = __esm({
|
|
|
4670
4897
|
if (this.options.githubToken) {
|
|
4671
4898
|
envWithoutNodeDebug.COPILOT_SDK_AUTH_TOKEN = this.options.githubToken;
|
|
4672
4899
|
}
|
|
4900
|
+
if (!this.options.cliPath) {
|
|
4901
|
+
throw new Error(
|
|
4902
|
+
"Path to Copilot CLI is required. Please provide it via the cliPath option, or use cliUrl to rely on a remote CLI."
|
|
4903
|
+
);
|
|
4904
|
+
}
|
|
4905
|
+
if (this.options.telemetry) {
|
|
4906
|
+
const t = this.options.telemetry;
|
|
4907
|
+
envWithoutNodeDebug.COPILOT_OTEL_ENABLED = "true";
|
|
4908
|
+
if (t.otlpEndpoint !== void 0)
|
|
4909
|
+
envWithoutNodeDebug.OTEL_EXPORTER_OTLP_ENDPOINT = t.otlpEndpoint;
|
|
4910
|
+
if (t.filePath !== void 0)
|
|
4911
|
+
envWithoutNodeDebug.COPILOT_OTEL_FILE_EXPORTER_PATH = t.filePath;
|
|
4912
|
+
if (t.exporterType !== void 0)
|
|
4913
|
+
envWithoutNodeDebug.COPILOT_OTEL_EXPORTER_TYPE = t.exporterType;
|
|
4914
|
+
if (t.sourceName !== void 0)
|
|
4915
|
+
envWithoutNodeDebug.COPILOT_OTEL_SOURCE_NAME = t.sourceName;
|
|
4916
|
+
if (t.captureContent !== void 0)
|
|
4917
|
+
envWithoutNodeDebug.OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT = String(
|
|
4918
|
+
t.captureContent
|
|
4919
|
+
);
|
|
4920
|
+
}
|
|
4673
4921
|
if (!existsSync(this.options.cliPath)) {
|
|
4674
4922
|
throw new Error(
|
|
4675
4923
|
`Copilot CLI not found at ${this.options.cliPath}. Ensure @github/copilot is installed.`
|
|
@@ -4769,8 +5017,6 @@ stderr: ${stderrOutput}`
|
|
|
4769
5017
|
} else {
|
|
4770
5018
|
reject(new Error(`CLI server exited with code ${code}`));
|
|
4771
5019
|
}
|
|
4772
|
-
} else if (this.options.autoRestart && this.state === "connected") {
|
|
4773
|
-
void this.reconnect();
|
|
4774
5020
|
}
|
|
4775
5021
|
});
|
|
4776
5022
|
setTimeout(() => {
|
|
@@ -4875,12 +5121,15 @@ stderr: ${stderrOutput}`
|
|
|
4875
5121
|
"hooks.invoke",
|
|
4876
5122
|
async (params) => await this.handleHooksInvoke(params)
|
|
4877
5123
|
);
|
|
5124
|
+
this.connection.onRequest(
|
|
5125
|
+
"systemMessage.transform",
|
|
5126
|
+
async (params) => await this.handleSystemMessageTransform(params)
|
|
5127
|
+
);
|
|
4878
5128
|
this.connection.onClose(() => {
|
|
4879
|
-
|
|
4880
|
-
void this.reconnect();
|
|
4881
|
-
}
|
|
5129
|
+
this.state = "disconnected";
|
|
4882
5130
|
});
|
|
4883
5131
|
this.connection.onError((_error) => {
|
|
5132
|
+
this.state = "disconnected";
|
|
4884
5133
|
});
|
|
4885
5134
|
}
|
|
4886
5135
|
handleSessionEventNotification(notification) {
|
|
@@ -4939,6 +5188,16 @@ stderr: ${stderrOutput}`
|
|
|
4939
5188
|
const output = await session._handleHooksInvoke(params.hookType, params.input);
|
|
4940
5189
|
return { output };
|
|
4941
5190
|
}
|
|
5191
|
+
async handleSystemMessageTransform(params) {
|
|
5192
|
+
if (!params || typeof params.sessionId !== "string" || !params.sections || typeof params.sections !== "object") {
|
|
5193
|
+
throw new Error("Invalid systemMessage.transform payload");
|
|
5194
|
+
}
|
|
5195
|
+
const session = this.sessions.get(params.sessionId);
|
|
5196
|
+
if (!session) {
|
|
5197
|
+
throw new Error(`Session not found: ${params.sessionId}`);
|
|
5198
|
+
}
|
|
5199
|
+
return await session._handleSystemMessageTransform(params.sections);
|
|
5200
|
+
}
|
|
4942
5201
|
// ========================================================================
|
|
4943
5202
|
// Protocol v2 backward-compatibility adapters
|
|
4944
5203
|
// ========================================================================
|
|
@@ -4967,11 +5226,15 @@ stderr: ${stderrOutput}`
|
|
|
4967
5226
|
};
|
|
4968
5227
|
}
|
|
4969
5228
|
try {
|
|
5229
|
+
const traceparent = params.traceparent;
|
|
5230
|
+
const tracestate = params.tracestate;
|
|
4970
5231
|
const invocation = {
|
|
4971
5232
|
sessionId: params.sessionId,
|
|
4972
5233
|
toolCallId: params.toolCallId,
|
|
4973
5234
|
toolName: params.toolName,
|
|
4974
|
-
arguments: params.arguments
|
|
5235
|
+
arguments: params.arguments,
|
|
5236
|
+
traceparent,
|
|
5237
|
+
tracestate
|
|
4975
5238
|
};
|
|
4976
5239
|
const result = await handler(params.arguments, invocation);
|
|
4977
5240
|
return { result: this.normalizeToolResultV2(result) };
|
|
@@ -5001,7 +5264,10 @@ stderr: ${stderrOutput}`
|
|
|
5001
5264
|
try {
|
|
5002
5265
|
const result = await session._handlePermissionRequestV2(params.permissionRequest);
|
|
5003
5266
|
return { result };
|
|
5004
|
-
} catch (
|
|
5267
|
+
} catch (error) {
|
|
5268
|
+
if (error instanceof Error && error.message === NO_RESULT_PERMISSION_V2_ERROR) {
|
|
5269
|
+
throw error;
|
|
5270
|
+
}
|
|
5005
5271
|
return {
|
|
5006
5272
|
result: {
|
|
5007
5273
|
kind: "denied-no-approval-rule-and-could-not-request-from-user"
|
|
@@ -5031,17 +5297,6 @@ stderr: ${stderrOutput}`
|
|
|
5031
5297
|
isToolResultObject(value) {
|
|
5032
5298
|
return typeof value === "object" && value !== null && "textResultForLlm" in value && typeof value.textResultForLlm === "string" && "resultType" in value;
|
|
5033
5299
|
}
|
|
5034
|
-
/**
|
|
5035
|
-
* Attempt to reconnect to the server
|
|
5036
|
-
*/
|
|
5037
|
-
async reconnect() {
|
|
5038
|
-
this.state = "disconnected";
|
|
5039
|
-
try {
|
|
5040
|
-
await this.stop();
|
|
5041
|
-
await this.start();
|
|
5042
|
-
} catch (_error) {
|
|
5043
|
-
}
|
|
5044
|
-
}
|
|
5045
5300
|
};
|
|
5046
5301
|
}
|
|
5047
5302
|
});
|
|
@@ -5050,10 +5305,24 @@ stderr: ${stderrOutput}`
|
|
|
5050
5305
|
function defineTool(name, config) {
|
|
5051
5306
|
return { name, ...config };
|
|
5052
5307
|
}
|
|
5053
|
-
var approveAll;
|
|
5308
|
+
var SYSTEM_PROMPT_SECTIONS, approveAll;
|
|
5054
5309
|
var init_types = __esm({
|
|
5055
5310
|
"node_modules/@github/copilot-sdk/dist/types.js"() {
|
|
5056
5311
|
"use strict";
|
|
5312
|
+
SYSTEM_PROMPT_SECTIONS = {
|
|
5313
|
+
identity: { description: "Agent identity preamble and mode statement" },
|
|
5314
|
+
tone: { description: "Response style, conciseness rules, output formatting preferences" },
|
|
5315
|
+
tool_efficiency: { description: "Tool usage patterns, parallel calling, batching guidelines" },
|
|
5316
|
+
environment_context: { description: "CWD, OS, git root, directory listing, available tools" },
|
|
5317
|
+
code_change_rules: { description: "Coding rules, linting/testing, ecosystem tools, style" },
|
|
5318
|
+
guidelines: { description: "Tips, behavioral best practices, behavioral guidelines" },
|
|
5319
|
+
safety: { description: "Environment limitations, prohibited actions, security policies" },
|
|
5320
|
+
tool_instructions: { description: "Per-tool usage instructions" },
|
|
5321
|
+
custom_instructions: { description: "Repository and organization custom instructions" },
|
|
5322
|
+
last_instructions: {
|
|
5323
|
+
description: "End-of-prompt instructions: parallel tool calling, persistence, task completion"
|
|
5324
|
+
}
|
|
5325
|
+
};
|
|
5057
5326
|
approveAll = () => ({ kind: "approved" });
|
|
5058
5327
|
}
|
|
5059
5328
|
});
|
|
@@ -5063,6 +5332,7 @@ var dist_exports = {};
|
|
|
5063
5332
|
__export(dist_exports, {
|
|
5064
5333
|
CopilotClient: () => CopilotClient,
|
|
5065
5334
|
CopilotSession: () => CopilotSession,
|
|
5335
|
+
SYSTEM_PROMPT_SECTIONS: () => SYSTEM_PROMPT_SECTIONS,
|
|
5066
5336
|
approveAll: () => approveAll,
|
|
5067
5337
|
defineTool: () => defineTool
|
|
5068
5338
|
});
|
|
@@ -5076,7 +5346,7 @@ var init_dist = __esm({
|
|
|
5076
5346
|
});
|
|
5077
5347
|
|
|
5078
5348
|
// src/cli.ts
|
|
5079
|
-
import { createRequire } from "module";
|
|
5349
|
+
import { createRequire as createRequire2 } from "module";
|
|
5080
5350
|
|
|
5081
5351
|
// packages/core/src/config.ts
|
|
5082
5352
|
var DEFAULT_MODEL = "claude-sonnet-4.6";
|
|
@@ -5332,6 +5602,18 @@ async function fileExists(filePath) {
|
|
|
5332
5602
|
return false;
|
|
5333
5603
|
}
|
|
5334
5604
|
}
|
|
5605
|
+
async function canSafeWrite(filePath, force) {
|
|
5606
|
+
const resolved = path.resolve(filePath);
|
|
5607
|
+
if (await hasSymlinkAncestor(resolved)) return false;
|
|
5608
|
+
try {
|
|
5609
|
+
const stat = await fs.lstat(resolved);
|
|
5610
|
+
if (stat.isSymbolicLink()) return false;
|
|
5611
|
+
return force;
|
|
5612
|
+
} catch (error) {
|
|
5613
|
+
if (error.code === "ENOENT") return true;
|
|
5614
|
+
throw error;
|
|
5615
|
+
}
|
|
5616
|
+
}
|
|
5335
5617
|
async function safeReadDir(dirPath) {
|
|
5336
5618
|
try {
|
|
5337
5619
|
return await fs.readdir(dirPath);
|
|
@@ -5339,10 +5621,47 @@ async function safeReadDir(dirPath) {
|
|
|
5339
5621
|
return [];
|
|
5340
5622
|
}
|
|
5341
5623
|
}
|
|
5624
|
+
function stripJsonComments(text) {
|
|
5625
|
+
let result = "";
|
|
5626
|
+
let i = 0;
|
|
5627
|
+
while (i < text.length) {
|
|
5628
|
+
if (text[i] === '"') {
|
|
5629
|
+
let j = i + 1;
|
|
5630
|
+
while (j < text.length) {
|
|
5631
|
+
if (text[j] === "\\") {
|
|
5632
|
+
j += 2;
|
|
5633
|
+
continue;
|
|
5634
|
+
}
|
|
5635
|
+
if (text[j] === '"') {
|
|
5636
|
+
j++;
|
|
5637
|
+
break;
|
|
5638
|
+
}
|
|
5639
|
+
j++;
|
|
5640
|
+
}
|
|
5641
|
+
result += text.slice(i, j);
|
|
5642
|
+
i = j;
|
|
5643
|
+
continue;
|
|
5644
|
+
}
|
|
5645
|
+
if (text[i] === "/" && text[i + 1] === "/") {
|
|
5646
|
+
i += 2;
|
|
5647
|
+
while (i < text.length && text[i] !== "\n") i++;
|
|
5648
|
+
continue;
|
|
5649
|
+
}
|
|
5650
|
+
if (text[i] === "/" && text[i + 1] === "*") {
|
|
5651
|
+
i += 2;
|
|
5652
|
+
while (i < text.length && !(text[i] === "*" && text[i + 1] === "/")) i++;
|
|
5653
|
+
if (i < text.length) i += 2;
|
|
5654
|
+
continue;
|
|
5655
|
+
}
|
|
5656
|
+
result += text[i];
|
|
5657
|
+
i++;
|
|
5658
|
+
}
|
|
5659
|
+
return result;
|
|
5660
|
+
}
|
|
5342
5661
|
async function readJson(filePath) {
|
|
5343
5662
|
try {
|
|
5344
5663
|
const raw = await fs.readFile(filePath, "utf8");
|
|
5345
|
-
return JSON.parse(raw);
|
|
5664
|
+
return JSON.parse(stripJsonComments(raw));
|
|
5346
5665
|
} catch {
|
|
5347
5666
|
return void 0;
|
|
5348
5667
|
}
|
|
@@ -5357,7 +5676,8 @@ var PACKAGE_MANAGERS = [
|
|
|
5357
5676
|
{ file: "pnpm-lock.yaml", name: "pnpm" },
|
|
5358
5677
|
{ file: "yarn.lock", name: "yarn" },
|
|
5359
5678
|
{ file: "package-lock.json", name: "npm" },
|
|
5360
|
-
{ file: "bun.lockb", name: "bun" }
|
|
5679
|
+
{ file: "bun.lockb", name: "bun" },
|
|
5680
|
+
{ file: "packages.lock.json", name: "nuget" }
|
|
5361
5681
|
];
|
|
5362
5682
|
async function analyzeRepo(repoPath) {
|
|
5363
5683
|
const files = await safeReadDir(repoPath);
|
|
@@ -5379,9 +5699,11 @@ async function analyzeRepo(repoPath) {
|
|
|
5379
5699
|
const hasRequirements = files.includes("requirements.txt");
|
|
5380
5700
|
const hasGoMod = files.includes("go.mod");
|
|
5381
5701
|
const hasCargo = files.includes("Cargo.toml");
|
|
5382
|
-
const
|
|
5383
|
-
(f) => f.endsWith(".csproj") || f.endsWith(".sln") || f.endsWith(".slnx")
|
|
5702
|
+
const hasDotnet = files.some(
|
|
5703
|
+
(f) => f.endsWith(".csproj") || f.endsWith(".fsproj") || f.endsWith(".sln") || f.endsWith(".slnx") || f === "global.json" || f === "Directory.Build.props"
|
|
5384
5704
|
);
|
|
5705
|
+
const hasCsproj = hasDotnet && files.some((f) => f.endsWith(".csproj"));
|
|
5706
|
+
const hasFsproj = files.some((f) => f.endsWith(".fsproj"));
|
|
5385
5707
|
const hasPomXml = files.includes("pom.xml");
|
|
5386
5708
|
const hasBuildGradle = files.includes("build.gradle") || files.includes("build.gradle.kts");
|
|
5387
5709
|
const hasGemfile = files.includes("Gemfile");
|
|
@@ -5396,7 +5718,8 @@ async function analyzeRepo(repoPath) {
|
|
|
5396
5718
|
if (hasPyProject || hasRequirements) analysis.languages.push("Python");
|
|
5397
5719
|
if (hasGoMod) analysis.languages.push("Go");
|
|
5398
5720
|
if (hasCargo) analysis.languages.push("Rust");
|
|
5399
|
-
if (hasCsproj) analysis.languages.push("C#");
|
|
5721
|
+
if (hasCsproj || hasDotnet && !hasFsproj) analysis.languages.push("C#");
|
|
5722
|
+
if (hasFsproj) analysis.languages.push("F#");
|
|
5400
5723
|
if (hasPomXml || hasBuildGradle) analysis.languages.push("Java");
|
|
5401
5724
|
if (hasGemfile) analysis.languages.push("Ruby");
|
|
5402
5725
|
if (hasComposerJson) analysis.languages.push("PHP");
|
|
@@ -5412,6 +5735,10 @@ async function analyzeRepo(repoPath) {
|
|
|
5412
5735
|
});
|
|
5413
5736
|
analysis.frameworks.push(...detectFrameworks(deps, files));
|
|
5414
5737
|
}
|
|
5738
|
+
if (hasDotnet) {
|
|
5739
|
+
const dotnetFrameworks = await detectDotnetFrameworks(repoPath);
|
|
5740
|
+
analysis.frameworks.push(...dotnetFrameworks);
|
|
5741
|
+
}
|
|
5415
5742
|
const workspace = await detectWorkspace(repoPath, files, rootPackageJson);
|
|
5416
5743
|
if (workspace) {
|
|
5417
5744
|
analysis.workspaceType = workspace.type;
|
|
@@ -5472,6 +5799,44 @@ function detectFrameworks(deps, files) {
|
|
|
5472
5799
|
if (deps.includes("fastify")) frameworks.push("Fastify");
|
|
5473
5800
|
return frameworks;
|
|
5474
5801
|
}
|
|
5802
|
+
async function detectDotnetFrameworks(repoPath) {
|
|
5803
|
+
const projectFiles = await fg("**/*.{csproj,fsproj}", {
|
|
5804
|
+
cwd: repoPath,
|
|
5805
|
+
onlyFiles: true,
|
|
5806
|
+
ignore: ["**/node_modules/**", "**/bin/**", "**/obj/**"]
|
|
5807
|
+
});
|
|
5808
|
+
const frameworks = [];
|
|
5809
|
+
for (const projFile of projectFiles) {
|
|
5810
|
+
try {
|
|
5811
|
+
const content = await fs2.readFile(path2.join(repoPath, projFile), "utf8");
|
|
5812
|
+
frameworks.push(...parseDotnetProject(content));
|
|
5813
|
+
} catch {
|
|
5814
|
+
}
|
|
5815
|
+
}
|
|
5816
|
+
return frameworks;
|
|
5817
|
+
}
|
|
5818
|
+
function parseDotnetProject(content) {
|
|
5819
|
+
const frameworks = [];
|
|
5820
|
+
const hasPackage = (pkg) => content.includes(`Include="${pkg}"`);
|
|
5821
|
+
if (content.includes('Sdk="Microsoft.NET.Sdk.Web"')) frameworks.push("ASP.NET Core");
|
|
5822
|
+
if (content.includes('Sdk="Microsoft.NET.Sdk.BlazorWebAssembly"'))
|
|
5823
|
+
frameworks.push("Blazor WebAssembly");
|
|
5824
|
+
if (hasPackage("Microsoft.AspNetCore") || hasPackage("Microsoft.AspNetCore.App"))
|
|
5825
|
+
frameworks.push("ASP.NET Core");
|
|
5826
|
+
if (hasPackage("Microsoft.AspNetCore.Components")) frameworks.push("Blazor");
|
|
5827
|
+
if (hasPackage("Microsoft.EntityFrameworkCore")) frameworks.push("Entity Framework");
|
|
5828
|
+
if (hasPackage("Microsoft.Maui.Controls")) frameworks.push(".NET MAUI");
|
|
5829
|
+
if (hasPackage("Xamarin.Forms") || hasPackage("Xamarin.Essentials")) frameworks.push("Xamarin");
|
|
5830
|
+
if (content.includes("<UseWPF>true</UseWPF>")) frameworks.push("WPF");
|
|
5831
|
+
if (content.includes("<UseWindowsForms>true</UseWindowsForms>")) frameworks.push("Windows Forms");
|
|
5832
|
+
if (hasPackage("xunit") || hasPackage("xunit.core")) frameworks.push("xUnit");
|
|
5833
|
+
if (hasPackage("NUnit") || hasPackage("nunit.framework")) frameworks.push("NUnit");
|
|
5834
|
+
if (hasPackage("MSTest.TestFramework")) frameworks.push("MSTest");
|
|
5835
|
+
if (frameworks.length === 0 && content.includes("<OutputType>Exe</OutputType>") && content.includes('Sdk="Microsoft.NET.Sdk"')) {
|
|
5836
|
+
frameworks.push("Console");
|
|
5837
|
+
}
|
|
5838
|
+
return frameworks;
|
|
5839
|
+
}
|
|
5475
5840
|
async function safeReadFile(filePath) {
|
|
5476
5841
|
try {
|
|
5477
5842
|
return await fs2.readFile(filePath, "utf8");
|
|
@@ -6626,14 +6991,14 @@ async function checkReposForInstructions(token, repos, onProgress) {
|
|
|
6626
6991
|
}
|
|
6627
6992
|
|
|
6628
6993
|
// packages/core/src/services/batch.ts
|
|
6629
|
-
import
|
|
6994
|
+
import path10 from "path";
|
|
6630
6995
|
|
|
6631
6996
|
// packages/core/src/utils/pr.ts
|
|
6632
6997
|
function buildInstructionsPrBody() {
|
|
6633
6998
|
return [
|
|
6634
|
-
"## \u{1F916}
|
|
6999
|
+
"## \u{1F916} Instructions Added",
|
|
6635
7000
|
"",
|
|
6636
|
-
"This PR adds a `.github/copilot-instructions.md` file to help
|
|
7001
|
+
"This PR adds a `.github/copilot-instructions.md` file to help AI coding assistants understand this codebase better.",
|
|
6637
7002
|
"",
|
|
6638
7003
|
"### What's Included",
|
|
6639
7004
|
"",
|
|
@@ -6645,7 +7010,7 @@ function buildInstructionsPrBody() {
|
|
|
6645
7010
|
"",
|
|
6646
7011
|
"### Benefits",
|
|
6647
7012
|
"",
|
|
6648
|
-
"With these instructions,
|
|
7013
|
+
"With these instructions, AI coding assistants will:",
|
|
6649
7014
|
"- Generate more contextually-aware code suggestions",
|
|
6650
7015
|
"- Follow project-specific patterns and conventions",
|
|
6651
7016
|
"- Understand the codebase structure",
|
|
@@ -6664,7 +7029,7 @@ function buildFullPrBody() {
|
|
|
6664
7029
|
"",
|
|
6665
7030
|
"| File | Purpose |",
|
|
6666
7031
|
"|------|---------|",
|
|
6667
|
-
"| `.github/copilot-instructions.md` | Custom instructions for
|
|
7032
|
+
"| `.github/copilot-instructions.md` | Custom instructions for AI coding assistants |",
|
|
6668
7033
|
"| `.vscode/settings.json` | VS Code settings for optimal AI assistance |",
|
|
6669
7034
|
"| `.vscode/mcp.json` | Model Context Protocol server configuration |",
|
|
6670
7035
|
"",
|
|
@@ -6678,7 +7043,7 @@ function buildFullPrBody() {
|
|
|
6678
7043
|
"",
|
|
6679
7044
|
"### Benefits",
|
|
6680
7045
|
"",
|
|
6681
|
-
"With these configurations,
|
|
7046
|
+
"With these configurations, AI coding assistants will:",
|
|
6682
7047
|
"- Generate more contextually-aware code suggestions",
|
|
6683
7048
|
"- Follow project-specific patterns and conventions",
|
|
6684
7049
|
"- Understand the codebase structure",
|
|
@@ -6688,7 +7053,7 @@ function buildFullPrBody() {
|
|
|
6688
7053
|
"",
|
|
6689
7054
|
"1. Merge this PR",
|
|
6690
7055
|
"2. Open the project in VS Code",
|
|
6691
|
-
"3. Start
|
|
7056
|
+
"3. Start using AI assistants \u2014 they now understand your project!",
|
|
6692
7057
|
"",
|
|
6693
7058
|
"---",
|
|
6694
7059
|
"*Generated by [AgentRC](https://github.com/microsoft/agentrc) - Prime your repos for AI*"
|
|
@@ -6910,7 +7275,7 @@ async function checkReposForInstructions2(token, repos, onProgress) {
|
|
|
6910
7275
|
|
|
6911
7276
|
// packages/core/src/services/instructions.ts
|
|
6912
7277
|
import fs5 from "fs/promises";
|
|
6913
|
-
import
|
|
7278
|
+
import path7 from "path";
|
|
6914
7279
|
|
|
6915
7280
|
// packages/core/src/services/copilot.ts
|
|
6916
7281
|
import fs4 from "fs/promises";
|
|
@@ -6953,8 +7318,32 @@ async function assertCopilotCliReady() {
|
|
|
6953
7318
|
async function listCopilotModels() {
|
|
6954
7319
|
const config = await assertCopilotCliReady();
|
|
6955
7320
|
const [cmd, args2] = buildExecArgs(config, ["--help"]);
|
|
6956
|
-
|
|
6957
|
-
|
|
7321
|
+
let stdout = "";
|
|
7322
|
+
let stderr = "";
|
|
7323
|
+
let execError = null;
|
|
7324
|
+
try {
|
|
7325
|
+
const result = await execFileAsync2(cmd, args2, { timeout: 5e3 });
|
|
7326
|
+
stdout = result.stdout;
|
|
7327
|
+
stderr = result.stderr;
|
|
7328
|
+
} catch (err) {
|
|
7329
|
+
execError = err;
|
|
7330
|
+
const e = err;
|
|
7331
|
+
stdout = e.stdout ?? "";
|
|
7332
|
+
stderr = e.stderr ?? "";
|
|
7333
|
+
}
|
|
7334
|
+
const fromStdout = extractModelChoices(stdout);
|
|
7335
|
+
if (fromStdout.length > 0) return fromStdout;
|
|
7336
|
+
const fromStderr = extractModelChoices(stderr);
|
|
7337
|
+
if (fromStderr.length > 0) return fromStderr;
|
|
7338
|
+
if (execError) {
|
|
7339
|
+
const e = execError;
|
|
7340
|
+
const details = e.stderr || e.stdout;
|
|
7341
|
+
const detailMsg = details ? `
|
|
7342
|
+
Copilot CLI output:
|
|
7343
|
+
${details}` : "";
|
|
7344
|
+
throw new Error(`Failed to list Copilot models: ${e.message}${detailMsg}`);
|
|
7345
|
+
}
|
|
7346
|
+
return [];
|
|
6958
7347
|
}
|
|
6959
7348
|
function buildExecArgs(config, extraArgs) {
|
|
6960
7349
|
if (config.cliArgs && config.cliArgs.length > 0) {
|
|
@@ -7163,7 +7552,9 @@ function normalizeError(error) {
|
|
|
7163
7552
|
}
|
|
7164
7553
|
function shouldFallbackToExternalServer(error) {
|
|
7165
7554
|
const message = normalizeError(error).message.toLowerCase();
|
|
7166
|
-
return message.includes("unknown option '--headless'") || message.includes("unknown option '--no-auto-update'") ||
|
|
7555
|
+
return message.includes("unknown option '--headless'") || message.includes("unknown option '--no-auto-update'") || // SDK's internal CLI resolution couldn't find the binary
|
|
7556
|
+
message.includes("copilot cli not found") || // Node's spawn() can't execute .bat/.cmd directly on Windows
|
|
7557
|
+
message.includes("spawn einval");
|
|
7167
7558
|
}
|
|
7168
7559
|
async function startExternalServer(cliConfig) {
|
|
7169
7560
|
const [cmd, args2] = buildExecArgs(cliConfig, ["--headless", "--log-level", "debug"]);
|
|
@@ -7281,8 +7672,11 @@ async function createCopilotClient(cliConfig) {
|
|
|
7281
7672
|
const desc = cliConfig.cliArgs ? `${cliConfig.cliPath} ${cliConfig.cliArgs.join(" ")}` : cliConfig.cliPath;
|
|
7282
7673
|
logCopilotDebug(`creating SDK client with cliPath=${desc} useStdio=false`);
|
|
7283
7674
|
const isNpx = /\bnpx(?:\.cmd)?$/iu.test(cliConfig.cliPath);
|
|
7284
|
-
|
|
7285
|
-
|
|
7675
|
+
const isBatShim = process.platform === "win32" && /\.(?:bat|cmd)$/iu.test(cliConfig.cliPath);
|
|
7676
|
+
if (isNpx || isBatShim) {
|
|
7677
|
+
logCopilotDebug(
|
|
7678
|
+
`${isNpx ? "npx wrapper" : ".bat/.cmd shim"} detected; using external server mode directly`
|
|
7679
|
+
);
|
|
7286
7680
|
const external = await startExternalServer(cliConfig);
|
|
7287
7681
|
const client = new sdk.CopilotClient({ cliUrl: external.cliUrl });
|
|
7288
7682
|
try {
|
|
@@ -7325,6 +7719,25 @@ async function createCopilotClient(cliConfig) {
|
|
|
7325
7719
|
}
|
|
7326
7720
|
}
|
|
7327
7721
|
|
|
7722
|
+
// packages/core/src/services/skills.ts
|
|
7723
|
+
import path6 from "path";
|
|
7724
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
7725
|
+
var overrideSkillsDir;
|
|
7726
|
+
function getBuiltinSkillsDir() {
|
|
7727
|
+
if (overrideSkillsDir) {
|
|
7728
|
+
return overrideSkillsDir;
|
|
7729
|
+
}
|
|
7730
|
+
const thisFile = fileURLToPath2(import.meta.url);
|
|
7731
|
+
const thisDir = path6.dirname(thisFile);
|
|
7732
|
+
if (thisDir.includes(path6.join("packages", "core", "src"))) {
|
|
7733
|
+
return path6.resolve(thisDir, "..", "..", "..", "..", "plugin", "skills");
|
|
7734
|
+
}
|
|
7735
|
+
return path6.resolve(thisDir, "skills");
|
|
7736
|
+
}
|
|
7737
|
+
function getSkillDirectory(_skillName) {
|
|
7738
|
+
return getBuiltinSkillsDir();
|
|
7739
|
+
}
|
|
7740
|
+
|
|
7328
7741
|
// packages/core/src/services/instructions.ts
|
|
7329
7742
|
async function detectExistingInstructions(repoPath, detailDirName = ".agents") {
|
|
7330
7743
|
const { agentsMdFiles, claudeMdFiles } = await findInstructionMarkerFiles(repoPath);
|
|
@@ -7348,7 +7761,7 @@ async function findInstructionMarkerFiles(repoPath) {
|
|
|
7348
7761
|
claudeMdFiles.push(relPath ? `${relPath}/${entry.name}` : entry.name);
|
|
7349
7762
|
}
|
|
7350
7763
|
} else if (entry.isDirectory()) {
|
|
7351
|
-
await walk(
|
|
7764
|
+
await walk(path7.join(dir, entry.name), relPath ? `${relPath}/${entry.name}` : entry.name);
|
|
7352
7765
|
}
|
|
7353
7766
|
}
|
|
7354
7767
|
}
|
|
@@ -7356,7 +7769,7 @@ async function findInstructionMarkerFiles(repoPath) {
|
|
|
7356
7769
|
return { agentsMdFiles: agentsMdFiles.sort(), claudeMdFiles: claudeMdFiles.sort() };
|
|
7357
7770
|
}
|
|
7358
7771
|
async function findModularInstructionFiles(repoPath) {
|
|
7359
|
-
const dir =
|
|
7772
|
+
const dir = path7.join(repoPath, ".github", "instructions");
|
|
7360
7773
|
const entries = await fs5.readdir(dir, { withFileTypes: true }).catch(() => []);
|
|
7361
7774
|
return entries.filter((e) => !e.isSymbolicLink() && e.isFile() && e.name.endsWith(".instructions.md")).map((e) => `.github/instructions/${e.name}`).sort();
|
|
7362
7775
|
}
|
|
@@ -7370,7 +7783,7 @@ async function findDetailFiles(repoPath, detailDirName) {
|
|
|
7370
7783
|
if (entry.isSymbolicLink()) continue;
|
|
7371
7784
|
if (entry.isDirectory()) {
|
|
7372
7785
|
if (entry.name === detailDirName) {
|
|
7373
|
-
const detailDir =
|
|
7786
|
+
const detailDir = path7.join(dir, entry.name);
|
|
7374
7787
|
const detailEntries = await fs5.readdir(detailDir, { withFileTypes: true }).catch(() => []);
|
|
7375
7788
|
for (const de of detailEntries) {
|
|
7376
7789
|
if (!de.isSymbolicLink() && de.isFile() && de.name.endsWith(".md")) {
|
|
@@ -7379,7 +7792,7 @@ async function findDetailFiles(repoPath, detailDirName) {
|
|
|
7379
7792
|
}
|
|
7380
7793
|
}
|
|
7381
7794
|
} else {
|
|
7382
|
-
await walk(
|
|
7795
|
+
await walk(path7.join(dir, entry.name), relPath ? `${relPath}/${entry.name}` : entry.name);
|
|
7383
7796
|
}
|
|
7384
7797
|
}
|
|
7385
7798
|
}
|
|
@@ -7461,11 +7874,11 @@ function isCopilotAuthError(error) {
|
|
|
7461
7874
|
return error instanceof Error && error.message.includes("Copilot CLI not logged in.");
|
|
7462
7875
|
}
|
|
7463
7876
|
function resolveAreaWorkingDirectory(repoPath, area) {
|
|
7464
|
-
const repoRoot =
|
|
7877
|
+
const repoRoot = path7.resolve(repoPath);
|
|
7465
7878
|
const rawWorkingDirectory = area?.workingDirectory ?? area?.path;
|
|
7466
7879
|
if (!rawWorkingDirectory) return repoRoot;
|
|
7467
|
-
const resolved = area?.workingDirectory ?
|
|
7468
|
-
if (resolved !== repoRoot && !resolved.startsWith(repoRoot +
|
|
7880
|
+
const resolved = area?.workingDirectory ? path7.resolve(repoRoot, rawWorkingDirectory) : path7.isAbsolute(rawWorkingDirectory) ? path7.resolve(rawWorkingDirectory) : path7.resolve(repoRoot, rawWorkingDirectory);
|
|
7881
|
+
if (resolved !== repoRoot && !resolved.startsWith(repoRoot + path7.sep)) {
|
|
7469
7882
|
throw new Error(`Invalid workingDirectory "${rawWorkingDirectory}": escapes repo boundary`);
|
|
7470
7883
|
}
|
|
7471
7884
|
return resolved;
|
|
@@ -7497,6 +7910,7 @@ async function generateCopilotInstructions(options) {
|
|
|
7497
7910
|
const preferredModel = options.model ?? DEFAULT_MODEL;
|
|
7498
7911
|
const systemContent = hasExistingInstructions ? "You are an expert codebase analyst. Your task is to generate a concise .github/copilot-instructions.md that complements existing instruction files. Use the available tools (glob, view, grep) to explore the codebase. When done, call the emit_file_content tool with the final markdown." : "You are an expert codebase analyst. Your task is to generate a concise .github/copilot-instructions.md file. Use the available tools (glob, view, grep) to explore the codebase. When done, call the emit_file_content tool with the final markdown.";
|
|
7499
7912
|
const { tool: emitTool, getContent } = await createEmitTool();
|
|
7913
|
+
const rootSkillDir = getSkillDirectory("root-instructions");
|
|
7500
7914
|
const session = await client.createSession({
|
|
7501
7915
|
model: preferredModel,
|
|
7502
7916
|
streaming: true,
|
|
@@ -7507,7 +7921,8 @@ async function generateCopilotInstructions(options) {
|
|
|
7507
7921
|
tools: [emitTool],
|
|
7508
7922
|
excludedTools: INSTRUCTION_GENERATION_EXCLUDED_TOOLS,
|
|
7509
7923
|
onPermissionRequest: READ_ONLY_PERMISSION_HANDLER,
|
|
7510
|
-
infiniteSessions: { enabled: false }
|
|
7924
|
+
infiniteSessions: { enabled: false },
|
|
7925
|
+
skillDirectories: [rootSkillDir]
|
|
7511
7926
|
});
|
|
7512
7927
|
await trySetAutopilot(session);
|
|
7513
7928
|
let content = "";
|
|
@@ -7528,22 +7943,8 @@ async function generateCopilotInstructions(options) {
|
|
|
7528
7943
|
sessionError = getSessionError(errorMsg);
|
|
7529
7944
|
}
|
|
7530
7945
|
});
|
|
7531
|
-
const prompt =
|
|
7532
|
-
|
|
7533
|
-
Fan out multiple Explore subagents to map out the codebase in parallel:
|
|
7534
|
-
1. Check for existing instruction files: glob for **/{.github/copilot-instructions.md,AGENT.md,CLAUDE.md,.cursorrules,README.md}
|
|
7535
|
-
2. Identify the tech stack: look at package.json, tsconfig.json, pyproject.toml, Cargo.toml, go.mod, *.csproj, *.sln, build.gradle, pom.xml, etc.
|
|
7536
|
-
3. Understand the structure: list key directories
|
|
7537
|
-
4. Detect monorepo structures: check for workspace configs (npm/pnpm/yarn workspaces, Cargo.toml [workspace], go.work, .sln solution files, settings.gradle include directives, pom.xml modules)
|
|
7538
|
-
|
|
7539
|
-
Generate concise instructions (~20-50 lines) covering:
|
|
7540
|
-
- Tech stack and architecture
|
|
7541
|
-
- Build/test commands
|
|
7542
|
-
- Project-specific conventions
|
|
7543
|
-
- Key files/directories
|
|
7544
|
-
- Monorepo structure and per-app layout (if this is a monorepo, describe the workspace organization, how apps relate to each other, and any shared libraries)
|
|
7545
|
-
${existingSection}
|
|
7546
|
-
When you have the complete markdown content, call the \`emit_file_content\` tool with it. Do NOT output the file content directly in chat.`;
|
|
7946
|
+
const prompt = `/root-instructions Analyze this codebase and generate a .github/copilot-instructions.md file.
|
|
7947
|
+
${existingSection}`;
|
|
7547
7948
|
progress("Analyzing codebase...");
|
|
7548
7949
|
let sendError;
|
|
7549
7950
|
try {
|
|
@@ -7578,8 +7979,9 @@ async function generateAreaInstructions(options) {
|
|
|
7578
7979
|
const applyToStr = applyToPatterns.join(", ");
|
|
7579
7980
|
progress(`Creating session for area "${area.name}"...`);
|
|
7580
7981
|
const preferredModel = options.model ?? DEFAULT_MODEL;
|
|
7581
|
-
const areaSystemContent = hasExistingInstructions ? `You are an expert codebase analyst. Your task is to generate a concise .instructions.md file for a specific area of a codebase. This file will be used as
|
|
7982
|
+
const areaSystemContent = hasExistingInstructions ? `You are an expert codebase analyst. Your task is to generate a concise .instructions.md file for a specific area of a codebase. This file will be used as an area instruction in VS Code, automatically applied when working on files matching certain patterns. This file should complement, not duplicate, existing instruction files. Use the Explore subagents and read-only tools to explore the codebase. When done, call the emit_file_content tool with the final markdown.` : `You are an expert codebase analyst. Your task is to generate a concise .instructions.md file for a specific area of a codebase. This file will be used as an area instruction in VS Code, automatically applied when working on files matching certain patterns. Use the Explore subagents and read-only tools to explore the codebase. When done, call the emit_file_content tool with the final markdown.`;
|
|
7582
7983
|
const { tool: emitTool, getContent } = await createEmitTool();
|
|
7984
|
+
const areaSkillDir = getSkillDirectory("area-instructions");
|
|
7583
7985
|
const session = await client.createSession({
|
|
7584
7986
|
model: preferredModel,
|
|
7585
7987
|
streaming: true,
|
|
@@ -7590,7 +7992,8 @@ async function generateAreaInstructions(options) {
|
|
|
7590
7992
|
tools: [emitTool],
|
|
7591
7993
|
excludedTools: INSTRUCTION_GENERATION_EXCLUDED_TOOLS,
|
|
7592
7994
|
onPermissionRequest: READ_ONLY_PERMISSION_HANDLER,
|
|
7593
|
-
infiniteSessions: { enabled: false }
|
|
7995
|
+
infiniteSessions: { enabled: false },
|
|
7996
|
+
skillDirectories: [areaSkillDir]
|
|
7594
7997
|
});
|
|
7595
7998
|
await trySetAutopilot(session);
|
|
7596
7999
|
let content = "";
|
|
@@ -7611,30 +8014,13 @@ async function generateAreaInstructions(options) {
|
|
|
7611
8014
|
sessionError = getSessionError(errorMsg);
|
|
7612
8015
|
}
|
|
7613
8016
|
});
|
|
7614
|
-
const prompt =
|
|
8017
|
+
const prompt = `/area-instructions Analyze the "${area.name}" area of this codebase and generate an area instruction file.
|
|
7615
8018
|
|
|
7616
8019
|
This area covers files matching: ${applyToStr}
|
|
7617
8020
|
${area.description ? `Description: ${area.description}` : ""}
|
|
7618
|
-
|
|
7619
|
-
|
|
7620
|
-
|
|
7621
|
-
2. Identify the tech stack, dependencies, and frameworks used in this area
|
|
7622
|
-
3. Look at key source files to understand patterns and conventions specific to this area
|
|
7623
|
-
|
|
7624
|
-
Generate concise instructions (~10-30 lines) covering:
|
|
7625
|
-
- What this area does and its role in the overall project
|
|
7626
|
-
- Area-specific tech stack, dependencies, and frameworks
|
|
7627
|
-
- Coding conventions and patterns specific to this area
|
|
7628
|
-
- Build/test commands relevant to this area (if different from root)
|
|
7629
|
-
- Key files and directory structure within this area
|
|
7630
|
-
|
|
7631
|
-
IMPORTANT:
|
|
7632
|
-
- Focus ONLY on this specific area, not the whole repo
|
|
7633
|
-
- Do NOT repeat repo-wide information (that goes in the root copilot-instructions.md)
|
|
7634
|
-
- Keep it complementary to root instructions
|
|
7635
|
-
${existingSection ? `- Do NOT duplicate content already covered by existing instruction files
|
|
7636
|
-
${existingSection}` : ""}
|
|
7637
|
-
- When you have the complete markdown content, call the \`emit_file_content\` tool with it. Do NOT output the file content directly in chat.`;
|
|
8021
|
+
${existingSection ? `
|
|
8022
|
+
Do NOT duplicate content already covered by existing instruction files
|
|
8023
|
+
${existingSection}` : ""}`;
|
|
7638
8024
|
progress(`Analyzing area "${area.name}"...`);
|
|
7639
8025
|
let sendError;
|
|
7640
8026
|
try {
|
|
@@ -7671,7 +8057,7 @@ ${body}
|
|
|
7671
8057
|
`;
|
|
7672
8058
|
}
|
|
7673
8059
|
function areaInstructionPath(repoPath, area) {
|
|
7674
|
-
return
|
|
8060
|
+
return path7.join(
|
|
7675
8061
|
repoPath,
|
|
7676
8062
|
".github",
|
|
7677
8063
|
"instructions",
|
|
@@ -7681,7 +8067,7 @@ function areaInstructionPath(repoPath, area) {
|
|
|
7681
8067
|
async function writeAreaInstruction(repoPath, area, body, force) {
|
|
7682
8068
|
const filePath = areaInstructionPath(repoPath, area);
|
|
7683
8069
|
if (!body.trim()) return { status: "empty", filePath };
|
|
7684
|
-
await ensureDir(
|
|
8070
|
+
await ensureDir(path7.dirname(filePath));
|
|
7685
8071
|
const { wrote, reason } = await safeWriteFile(
|
|
7686
8072
|
filePath,
|
|
7687
8073
|
buildAreaInstructionContent(area, body),
|
|
@@ -7693,13 +8079,13 @@ async function writeAreaInstruction(repoPath, area, body, force) {
|
|
|
7693
8079
|
return { status: "written", filePath };
|
|
7694
8080
|
}
|
|
7695
8081
|
async function writeInstructionFile(repoPath, relativePath, content, force) {
|
|
7696
|
-
const resolvedRoot =
|
|
7697
|
-
const filePath =
|
|
7698
|
-
if (!filePath.startsWith(resolvedRoot +
|
|
8082
|
+
const resolvedRoot = path7.resolve(repoPath);
|
|
8083
|
+
const filePath = path7.resolve(repoPath, relativePath);
|
|
8084
|
+
if (!filePath.startsWith(resolvedRoot + path7.sep) && filePath !== resolvedRoot) {
|
|
7699
8085
|
throw new Error(`Invalid path: escapes repository root (${relativePath})`);
|
|
7700
8086
|
}
|
|
7701
8087
|
if (!content.trim()) return { status: "empty", filePath };
|
|
7702
|
-
await ensureDir(
|
|
8088
|
+
await ensureDir(path7.dirname(filePath));
|
|
7703
8089
|
const { wrote, reason } = await safeWriteFile(filePath, content, Boolean(force));
|
|
7704
8090
|
if (!wrote) {
|
|
7705
8091
|
return { status: reason === "symlink" ? "symlink" : "skipped", filePath };
|
|
@@ -7783,6 +8169,7 @@ async function generateNestedHub(client, options) {
|
|
|
7783
8169
|
const existingCtx = await detectExistingInstructions(options.repoPath, options.detailDir);
|
|
7784
8170
|
const existingSection = buildExistingInstructionsSection(existingCtx);
|
|
7785
8171
|
const { tool: emitTool, getContent } = await createEmitTool();
|
|
8172
|
+
const nestedSkillDir = getSkillDirectory("nested-hub");
|
|
7786
8173
|
const session = await client.createSession({
|
|
7787
8174
|
model,
|
|
7788
8175
|
streaming: true,
|
|
@@ -7793,7 +8180,8 @@ async function generateNestedHub(client, options) {
|
|
|
7793
8180
|
tools: [emitTool],
|
|
7794
8181
|
excludedTools: INSTRUCTION_GENERATION_EXCLUDED_TOOLS,
|
|
7795
8182
|
onPermissionRequest: READ_ONLY_PERMISSION_HANDLER,
|
|
7796
|
-
infiniteSessions: { enabled: false }
|
|
8183
|
+
infiniteSessions: { enabled: false },
|
|
8184
|
+
skillDirectories: [nestedSkillDir]
|
|
7797
8185
|
});
|
|
7798
8186
|
await trySetAutopilot(session);
|
|
7799
8187
|
let content = "";
|
|
@@ -7825,29 +8213,14 @@ ${options.childAreas.map((c) => `- ${c.name} (${c.path ?? "unknown path"})`).joi
|
|
|
7825
8213
|
Include a "## Sub-Projects" section with links to each child's AGENTS.md.` : "";
|
|
7826
8214
|
const parentContext = options.area?.parentArea ? `
|
|
7827
8215
|
This is a sub-project of "${options.area.parentArea}". Include a note linking back to the parent area.` : "";
|
|
7828
|
-
const prompt =
|
|
7829
|
-
|
|
7830
|
-
Use tools to explore the codebase structure, tech stack, and conventions.
|
|
8216
|
+
const prompt = `/nested-hub Generate a lean AGENTS.md hub file (~90-120 lines).${areaContext}${parentContext}
|
|
7831
8217
|
|
|
7832
|
-
|
|
7833
|
-
- Project overview and purpose
|
|
7834
|
-
- Key concepts and architecture
|
|
7835
|
-
- Coding conventions and guardrails
|
|
7836
|
-
- A "## Detailed Instructions" section listing links to detail files in \`${options.detailDir}/\`${childContext}
|
|
8218
|
+
Detail files go in \`${options.detailDir}/\`.${childContext}
|
|
7837
8219
|
|
|
7838
|
-
|
|
7839
|
-
|
|
7840
|
-
|
|
7841
|
-
|
|
7842
|
-
|
|
7843
|
-
Recommend 3-5 topics that would benefit from deep-dive detail files. Each slug becomes a filename: \`${options.detailDir}/{slug}.md\`.
|
|
7844
|
-
|
|
7845
|
-
IMPORTANT:
|
|
7846
|
-
- Keep the hub LEAN \u2014 overview and guardrails only, details go in separate files
|
|
7847
|
-
- The JSON block will be parsed and removed from the final output
|
|
7848
|
-
${existingSection ? `- Do NOT duplicate content from existing instruction files
|
|
7849
|
-
${existingSection}` : ""}
|
|
7850
|
-
- When you have the complete markdown content (including the trailing JSON topic block), call the \`emit_file_content\` tool with it. Do NOT output the content directly in chat.`;
|
|
8220
|
+
Recommend 3-5 topics for deep-dive detail files. Each slug becomes: \`${options.detailDir}/{slug}.md\`.
|
|
8221
|
+
${existingSection ? `
|
|
8222
|
+
Do NOT duplicate content from existing instruction files
|
|
8223
|
+
${existingSection}` : ""}`;
|
|
7851
8224
|
let sendError;
|
|
7852
8225
|
try {
|
|
7853
8226
|
await session.sendAndWait({ prompt }, 18e4);
|
|
@@ -7871,6 +8244,7 @@ async function generateNestedDetail(client, options) {
|
|
|
7871
8244
|
});
|
|
7872
8245
|
const model = options.model ?? DEFAULT_MODEL;
|
|
7873
8246
|
const { tool: emitTool, getContent } = await createEmitTool();
|
|
8247
|
+
const detailSkillDir = getSkillDirectory("nested-detail");
|
|
7874
8248
|
const session = await client.createSession({
|
|
7875
8249
|
model,
|
|
7876
8250
|
streaming: true,
|
|
@@ -7881,7 +8255,8 @@ async function generateNestedDetail(client, options) {
|
|
|
7881
8255
|
tools: [emitTool],
|
|
7882
8256
|
excludedTools: INSTRUCTION_GENERATION_EXCLUDED_TOOLS,
|
|
7883
8257
|
onPermissionRequest: READ_ONLY_PERMISSION_HANDLER,
|
|
7884
|
-
infiniteSessions: { enabled: false }
|
|
8258
|
+
infiniteSessions: { enabled: false },
|
|
8259
|
+
skillDirectories: [detailSkillDir]
|
|
7885
8260
|
});
|
|
7886
8261
|
await trySetAutopilot(session);
|
|
7887
8262
|
let content = "";
|
|
@@ -7903,22 +8278,11 @@ async function generateNestedDetail(client, options) {
|
|
|
7903
8278
|
}
|
|
7904
8279
|
});
|
|
7905
8280
|
const areaContext = options.area ? `Focus on the "${options.area.name}" area (files matching: ${Array.isArray(options.area.applyTo) ? options.area.applyTo.join(", ") : options.area.applyTo}).` : "Focus on the entire repository.";
|
|
7906
|
-
const prompt =
|
|
8281
|
+
const prompt = `/nested-detail Generate a deep-dive instruction file about "${options.topic.title}" for this codebase.
|
|
7907
8282
|
${areaContext}
|
|
7908
8283
|
|
|
7909
8284
|
Topic: ${options.topic.title}
|
|
7910
|
-
Description: ${options.topic.description}
|
|
7911
|
-
|
|
7912
|
-
Use tools to explore the codebase and understand the specific patterns, APIs, and conventions related to this topic.
|
|
7913
|
-
|
|
7914
|
-
The file should:
|
|
7915
|
-
- Start with \`# ${options.topic.title}\`
|
|
7916
|
-
- Include \`**When to read:** {one-line trigger condition}\` right after the heading
|
|
7917
|
-
- Cover ~50-100 lines of practical, actionable guidance
|
|
7918
|
-
- Include code patterns and examples found in the actual codebase
|
|
7919
|
-
- Be specific to this codebase, not generic advice
|
|
7920
|
-
|
|
7921
|
-
When you have the complete markdown content, call the \`emit_file_content\` tool with it. Do NOT output the content directly in chat.`;
|
|
8285
|
+
Description: ${options.topic.description}`;
|
|
7922
8286
|
let sendError;
|
|
7923
8287
|
try {
|
|
7924
8288
|
await session.sendAndWait({ prompt }, 18e4);
|
|
@@ -7953,7 +8317,7 @@ async function generateNestedInstructions(options) {
|
|
|
7953
8317
|
onProgress: options.onProgress
|
|
7954
8318
|
});
|
|
7955
8319
|
const basePath = options.area?.path ?? ".";
|
|
7956
|
-
const hubRelativePath =
|
|
8320
|
+
const hubRelativePath = path7.join(basePath, "AGENTS.md");
|
|
7957
8321
|
let finalHubContent = hubContent;
|
|
7958
8322
|
if (options.area) {
|
|
7959
8323
|
finalHubContent = `${buildAreaFrontmatter(options.area)}
|
|
@@ -7977,7 +8341,7 @@ ${hubContent}`;
|
|
|
7977
8341
|
});
|
|
7978
8342
|
if (detailContent) {
|
|
7979
8343
|
result.details.push({
|
|
7980
|
-
relativePath:
|
|
8344
|
+
relativePath: path7.join(basePath, options.detailDir, `${topic.slug}.md`),
|
|
7981
8345
|
content: detailContent,
|
|
7982
8346
|
topic: topic.title
|
|
7983
8347
|
});
|
|
@@ -7992,7 +8356,7 @@ ${hubContent}`;
|
|
|
7992
8356
|
}
|
|
7993
8357
|
if (options.claudeMd) {
|
|
7994
8358
|
result.claudeMd = {
|
|
7995
|
-
relativePath:
|
|
8359
|
+
relativePath: path7.join(basePath, "CLAUDE.md"),
|
|
7996
8360
|
content: "@AGENTS.md\n"
|
|
7997
8361
|
};
|
|
7998
8362
|
}
|
|
@@ -8015,11 +8379,11 @@ async function generateNestedAreaInstructions(options) {
|
|
|
8015
8379
|
|
|
8016
8380
|
// packages/core/src/services/readiness.ts
|
|
8017
8381
|
import fs7 from "fs/promises";
|
|
8018
|
-
import
|
|
8382
|
+
import path9 from "path";
|
|
8019
8383
|
|
|
8020
8384
|
// packages/core/src/services/policy.ts
|
|
8021
8385
|
import fs6 from "fs/promises";
|
|
8022
|
-
import
|
|
8386
|
+
import path8 from "path";
|
|
8023
8387
|
var DEFAULT_PASS_RATE = 0.8;
|
|
8024
8388
|
function validatePolicyConfig(obj, source, format = "module") {
|
|
8025
8389
|
if (typeof obj !== "object" || obj === null) {
|
|
@@ -8112,8 +8476,8 @@ function parsePolicySources(raw) {
|
|
|
8112
8476
|
}
|
|
8113
8477
|
async function loadPolicy(source, options) {
|
|
8114
8478
|
const jsonOnly = options?.jsonOnly ?? false;
|
|
8115
|
-
if (source.startsWith(".") ||
|
|
8116
|
-
const resolved =
|
|
8479
|
+
if (source.startsWith(".") || path8.isAbsolute(source)) {
|
|
8480
|
+
const resolved = path8.resolve(source);
|
|
8117
8481
|
if (resolved.endsWith(".json")) {
|
|
8118
8482
|
const data = await readJson(resolved);
|
|
8119
8483
|
if (!data) {
|
|
@@ -8140,7 +8504,7 @@ async function loadPolicy(source, options) {
|
|
|
8140
8504
|
}
|
|
8141
8505
|
try {
|
|
8142
8506
|
const raw = await fs6.readFile(resolved, "utf8");
|
|
8143
|
-
const data = JSON.parse(raw);
|
|
8507
|
+
const data = JSON.parse(stripJsonComments(raw));
|
|
8144
8508
|
return validatePolicyConfig(data, source, "json");
|
|
8145
8509
|
} catch {
|
|
8146
8510
|
throw new Error(
|
|
@@ -8733,7 +9097,7 @@ function getLevelName(level) {
|
|
|
8733
9097
|
function getLevelDescription(level) {
|
|
8734
9098
|
const descriptions = {
|
|
8735
9099
|
1: "Repo builds, tests run, and basic tooling (linter, lockfile) is in place. AI agents can clone and get started.",
|
|
8736
|
-
2: "README, CONTRIBUTING guide, and custom
|
|
9100
|
+
2: "README, CONTRIBUTING guide, and custom instructions exist. Agents understand project context and conventions.",
|
|
8737
9101
|
3: "CI/CD, security policies, CODEOWNERS, and observability are configured. Agents operate within well-defined guardrails.",
|
|
8738
9102
|
4: "MCP servers, custom agents, and AI skills are set up. Agents have deep integration with project-specific tools and workflows.",
|
|
8739
9103
|
5: "Full AI-native development: agents can independently plan, implement, test, and ship changes with minimal human oversight."
|
|
@@ -8744,7 +9108,7 @@ async function runReadinessReport(options) {
|
|
|
8744
9108
|
const repoPath = options.repoPath;
|
|
8745
9109
|
const analysis = await analyzeRepo(repoPath);
|
|
8746
9110
|
const rootFiles = await safeReadDir(repoPath);
|
|
8747
|
-
const rootPackageJson = await readJson(
|
|
9111
|
+
const rootPackageJson = await readJson(path9.join(repoPath, "package.json"));
|
|
8748
9112
|
const apps = analysis.apps?.length ? analysis.apps : [];
|
|
8749
9113
|
const context = {
|
|
8750
9114
|
repoPath,
|
|
@@ -9053,7 +9417,7 @@ function buildCriteria() {
|
|
|
9053
9417
|
impact: "medium",
|
|
9054
9418
|
effort: "low",
|
|
9055
9419
|
check: async (context) => {
|
|
9056
|
-
const found = await fileExists(
|
|
9420
|
+
const found = await fileExists(path9.join(context.repoPath, "CONTRIBUTING.md"));
|
|
9057
9421
|
return {
|
|
9058
9422
|
status: found ? "pass" : "fail",
|
|
9059
9423
|
reason: found ? void 0 : "Missing CONTRIBUTING.md for contributor workflows."
|
|
@@ -9154,7 +9518,7 @@ function buildCriteria() {
|
|
|
9154
9518
|
impact: "high",
|
|
9155
9519
|
effort: "low",
|
|
9156
9520
|
check: async (context) => {
|
|
9157
|
-
const found = await fileExists(
|
|
9521
|
+
const found = await fileExists(path9.join(context.repoPath, "SECURITY.md"));
|
|
9158
9522
|
return {
|
|
9159
9523
|
status: found ? "pass" : "fail",
|
|
9160
9524
|
reason: found ? void 0 : "Missing SECURITY.md policy."
|
|
@@ -9170,7 +9534,7 @@ function buildCriteria() {
|
|
|
9170
9534
|
impact: "medium",
|
|
9171
9535
|
effort: "medium",
|
|
9172
9536
|
check: async (context) => {
|
|
9173
|
-
const found = await fileExists(
|
|
9537
|
+
const found = await fileExists(path9.join(context.repoPath, ".github", "dependabot.yml"));
|
|
9174
9538
|
return {
|
|
9175
9539
|
status: found ? "pass" : "fail",
|
|
9176
9540
|
reason: found ? void 0 : "Missing .github/dependabot.yml configuration."
|
|
@@ -9198,7 +9562,7 @@ function buildCriteria() {
|
|
|
9198
9562
|
},
|
|
9199
9563
|
{
|
|
9200
9564
|
id: "custom-instructions",
|
|
9201
|
-
title: "Custom
|
|
9565
|
+
title: "Custom instructions or agent guidance",
|
|
9202
9566
|
pillar: "ai-tooling",
|
|
9203
9567
|
level: 1,
|
|
9204
9568
|
scope: "repo",
|
|
@@ -9209,7 +9573,7 @@ function buildCriteria() {
|
|
|
9209
9573
|
if (rootFound.length === 0) {
|
|
9210
9574
|
return {
|
|
9211
9575
|
status: "fail",
|
|
9212
|
-
reason: "Missing custom
|
|
9576
|
+
reason: "Missing custom instructions (e.g. copilot-instructions.md, CLAUDE.md, AGENTS.md, .cursorrules).",
|
|
9213
9577
|
evidence: [
|
|
9214
9578
|
"copilot-instructions.md",
|
|
9215
9579
|
"CLAUDE.md",
|
|
@@ -9225,13 +9589,13 @@ function buildCriteria() {
|
|
|
9225
9589
|
if (fileBasedInstructions.length === 0) {
|
|
9226
9590
|
return {
|
|
9227
9591
|
status: "pass",
|
|
9228
|
-
reason: `Root instructions found, but no
|
|
9592
|
+
reason: `Root instructions found, but no area instructions for ${areas.length} detected areas. Run \`agentrc instructions --areas\` to generate.`,
|
|
9229
9593
|
evidence: [...rootFound, ...areas.map((a) => `${a.name}: missing .instructions.md`)]
|
|
9230
9594
|
};
|
|
9231
9595
|
}
|
|
9232
9596
|
return {
|
|
9233
9597
|
status: "pass",
|
|
9234
|
-
reason: `Root + ${fileBasedInstructions.length}
|
|
9598
|
+
reason: `Root + ${fileBasedInstructions.length} area instruction(s) found.`,
|
|
9235
9599
|
evidence: [...rootFound, ...fileBasedInstructions]
|
|
9236
9600
|
};
|
|
9237
9601
|
}
|
|
@@ -9376,7 +9740,7 @@ function buildCriteria() {
|
|
|
9376
9740
|
if (area?.scripts?.build) {
|
|
9377
9741
|
return { status: "pass" };
|
|
9378
9742
|
}
|
|
9379
|
-
const pkgPath =
|
|
9743
|
+
const pkgPath = path9.join(context.areaPath, "package.json");
|
|
9380
9744
|
const pkg = await readJson(pkgPath);
|
|
9381
9745
|
const scripts = pkg?.scripts ?? {};
|
|
9382
9746
|
const found = Boolean(scripts.build);
|
|
@@ -9401,7 +9765,7 @@ function buildCriteria() {
|
|
|
9401
9765
|
if (area?.scripts?.test) {
|
|
9402
9766
|
return { status: "pass" };
|
|
9403
9767
|
}
|
|
9404
|
-
const pkgPath =
|
|
9768
|
+
const pkgPath = path9.join(context.areaPath, "package.json");
|
|
9405
9769
|
const pkg = await readJson(pkgPath);
|
|
9406
9770
|
const scripts = pkg?.scripts ?? {};
|
|
9407
9771
|
const found = Boolean(scripts.test);
|
|
@@ -9424,7 +9788,7 @@ function buildCriteria() {
|
|
|
9424
9788
|
return { status: "skip", reason: "No area context." };
|
|
9425
9789
|
}
|
|
9426
9790
|
const sanitized = sanitizeAreaName(area.name);
|
|
9427
|
-
const instructionPath =
|
|
9791
|
+
const instructionPath = path9.join(
|
|
9428
9792
|
context.repoPath,
|
|
9429
9793
|
".github",
|
|
9430
9794
|
"instructions",
|
|
@@ -9445,7 +9809,7 @@ function buildExtras() {
|
|
|
9445
9809
|
id: "agents-doc",
|
|
9446
9810
|
title: "AGENTS.md present",
|
|
9447
9811
|
check: async (context) => {
|
|
9448
|
-
const found = await fileExists(
|
|
9812
|
+
const found = await fileExists(path9.join(context.repoPath, "AGENTS.md"));
|
|
9449
9813
|
return {
|
|
9450
9814
|
status: found ? "pass" : "fail",
|
|
9451
9815
|
reason: found ? void 0 : "Missing AGENTS.md to guide coding agents."
|
|
@@ -9610,11 +9974,11 @@ async function hasTypecheckConfig(repoPath) {
|
|
|
9610
9974
|
]);
|
|
9611
9975
|
}
|
|
9612
9976
|
async function hasGithubWorkflows(repoPath) {
|
|
9613
|
-
return fileExists(
|
|
9977
|
+
return fileExists(path9.join(repoPath, ".github", "workflows"));
|
|
9614
9978
|
}
|
|
9615
9979
|
async function hasCodeowners(repoPath) {
|
|
9616
|
-
const root = await fileExists(
|
|
9617
|
-
const github = await fileExists(
|
|
9980
|
+
const root = await fileExists(path9.join(repoPath, "CODEOWNERS"));
|
|
9981
|
+
const github = await fileExists(path9.join(repoPath, ".github", "CODEOWNERS"));
|
|
9618
9982
|
return root || github;
|
|
9619
9983
|
}
|
|
9620
9984
|
async function hasLicense(repoPath) {
|
|
@@ -9622,9 +9986,9 @@ async function hasLicense(repoPath) {
|
|
|
9622
9986
|
return files.some((file) => file.toLowerCase().startsWith("license"));
|
|
9623
9987
|
}
|
|
9624
9988
|
async function hasPullRequestTemplate(repoPath) {
|
|
9625
|
-
const direct = await fileExists(
|
|
9989
|
+
const direct = await fileExists(path9.join(repoPath, ".github", "PULL_REQUEST_TEMPLATE.md"));
|
|
9626
9990
|
if (direct) return true;
|
|
9627
|
-
const dir =
|
|
9991
|
+
const dir = path9.join(repoPath, ".github", "PULL_REQUEST_TEMPLATE");
|
|
9628
9992
|
try {
|
|
9629
9993
|
const entries = await fs7.readdir(dir);
|
|
9630
9994
|
return entries.some((entry) => entry.toLowerCase().endsWith(".md"));
|
|
@@ -9633,14 +9997,14 @@ async function hasPullRequestTemplate(repoPath) {
|
|
|
9633
9997
|
}
|
|
9634
9998
|
}
|
|
9635
9999
|
async function hasPrecommitConfig(repoPath) {
|
|
9636
|
-
const precommit = await fileExists(
|
|
10000
|
+
const precommit = await fileExists(path9.join(repoPath, ".pre-commit-config.yaml"));
|
|
9637
10001
|
if (precommit) return true;
|
|
9638
|
-
return fileExists(
|
|
10002
|
+
return fileExists(path9.join(repoPath, ".husky"));
|
|
9639
10003
|
}
|
|
9640
10004
|
async function hasArchitectureDoc(repoPath) {
|
|
9641
10005
|
const files = await safeReadDir(repoPath);
|
|
9642
10006
|
if (files.some((file) => file.toLowerCase() === "architecture.md")) return true;
|
|
9643
|
-
return fileExists(
|
|
10007
|
+
return fileExists(path9.join(repoPath, "docs", "architecture.md"));
|
|
9644
10008
|
}
|
|
9645
10009
|
async function hasCustomInstructions(repoPath) {
|
|
9646
10010
|
const found = [];
|
|
@@ -9657,14 +10021,14 @@ async function hasCustomInstructions(repoPath) {
|
|
|
9657
10021
|
"copilot-instructions.md"
|
|
9658
10022
|
];
|
|
9659
10023
|
for (const candidate of candidates) {
|
|
9660
|
-
if (await fileExists(
|
|
10024
|
+
if (await fileExists(path9.join(repoPath, candidate))) {
|
|
9661
10025
|
found.push(candidate);
|
|
9662
10026
|
}
|
|
9663
10027
|
}
|
|
9664
10028
|
return found;
|
|
9665
10029
|
}
|
|
9666
10030
|
async function hasFileBasedInstructions(repoPath) {
|
|
9667
|
-
const instructionsDir =
|
|
10031
|
+
const instructionsDir = path9.join(repoPath, ".github", "instructions");
|
|
9668
10032
|
try {
|
|
9669
10033
|
const entries = await fs7.readdir(instructionsDir);
|
|
9670
10034
|
return entries.filter((e) => e.endsWith(".instructions.md")).map((e) => `.github/instructions/${e}`);
|
|
@@ -9692,7 +10056,7 @@ async function checkInstructionConsistency(repoPath, foundFiles) {
|
|
|
9692
10056
|
}
|
|
9693
10057
|
const realPathMap = /* @__PURE__ */ new Map();
|
|
9694
10058
|
for (const file of foundFiles) {
|
|
9695
|
-
const fullPath =
|
|
10059
|
+
const fullPath = path9.join(repoPath, file);
|
|
9696
10060
|
try {
|
|
9697
10061
|
const real = await fs7.realpath(fullPath);
|
|
9698
10062
|
const group = realPathMap.get(real) ?? [];
|
|
@@ -9709,7 +10073,7 @@ async function checkInstructionConsistency(repoPath, foundFiles) {
|
|
|
9709
10073
|
const contents = [];
|
|
9710
10074
|
for (const group of groups) {
|
|
9711
10075
|
try {
|
|
9712
|
-
contents.push(await fs7.readFile(
|
|
10076
|
+
contents.push(await fs7.readFile(path9.join(repoPath, group[0]), "utf8"));
|
|
9713
10077
|
} catch {
|
|
9714
10078
|
contents.push("");
|
|
9715
10079
|
}
|
|
@@ -9728,17 +10092,17 @@ async function checkInstructionConsistency(repoPath, foundFiles) {
|
|
|
9728
10092
|
}
|
|
9729
10093
|
async function hasMcpConfig(repoPath) {
|
|
9730
10094
|
const found = [];
|
|
9731
|
-
if (await fileExists(
|
|
10095
|
+
if (await fileExists(path9.join(repoPath, ".vscode", "mcp.json"))) {
|
|
9732
10096
|
found.push(".vscode/mcp.json");
|
|
9733
10097
|
}
|
|
9734
|
-
if (await fileExists(
|
|
10098
|
+
if (await fileExists(path9.join(repoPath, "mcp.json"))) {
|
|
9735
10099
|
found.push("mcp.json");
|
|
9736
10100
|
}
|
|
9737
|
-
const settings = await readJson(
|
|
10101
|
+
const settings = await readJson(path9.join(repoPath, ".vscode", "settings.json"));
|
|
9738
10102
|
if (settings && (settings["mcp"] || settings["github.copilot.chat.mcp.enabled"])) {
|
|
9739
10103
|
found.push(".vscode/settings.json (mcp section)");
|
|
9740
10104
|
}
|
|
9741
|
-
if (await fileExists(
|
|
10105
|
+
if (await fileExists(path9.join(repoPath, ".claude", "mcp.json"))) {
|
|
9742
10106
|
found.push(".claude/mcp.json");
|
|
9743
10107
|
}
|
|
9744
10108
|
return found;
|
|
@@ -9747,13 +10111,13 @@ async function hasCustomAgents(repoPath) {
|
|
|
9747
10111
|
const found = [];
|
|
9748
10112
|
const agentDirs = [".github/agents", ".copilot/agents", ".github/copilot/agents"];
|
|
9749
10113
|
for (const dir of agentDirs) {
|
|
9750
|
-
if (await fileExists(
|
|
10114
|
+
if (await fileExists(path9.join(repoPath, dir))) {
|
|
9751
10115
|
found.push(dir);
|
|
9752
10116
|
}
|
|
9753
10117
|
}
|
|
9754
10118
|
const agentFiles = [".github/copilot-agents.yml", ".github/copilot-agents.yaml"];
|
|
9755
10119
|
for (const agentFile of agentFiles) {
|
|
9756
|
-
if (await fileExists(
|
|
10120
|
+
if (await fileExists(path9.join(repoPath, agentFile))) {
|
|
9757
10121
|
found.push(agentFile);
|
|
9758
10122
|
}
|
|
9759
10123
|
}
|
|
@@ -9768,7 +10132,7 @@ async function hasCopilotSkills(repoPath) {
|
|
|
9768
10132
|
".github/copilot/skills"
|
|
9769
10133
|
];
|
|
9770
10134
|
for (const dir of skillDirs) {
|
|
9771
|
-
if (await fileExists(
|
|
10135
|
+
if (await fileExists(path9.join(repoPath, dir))) {
|
|
9772
10136
|
found.push(dir);
|
|
9773
10137
|
}
|
|
9774
10138
|
}
|
|
@@ -9821,7 +10185,7 @@ async function processRepo(params) {
|
|
|
9821
10185
|
} = params;
|
|
9822
10186
|
try {
|
|
9823
10187
|
progress?.update(`${label}: Cloning...`);
|
|
9824
|
-
const cacheRoot =
|
|
10188
|
+
const cacheRoot = path10.join(process.cwd(), ".agentrc-cache");
|
|
9825
10189
|
const repoPath = validateCachePath(cacheRoot, ...cacheParts);
|
|
9826
10190
|
await ensureDir(repoPath);
|
|
9827
10191
|
if (!await isGitRepo(repoPath)) {
|
|
@@ -9847,8 +10211,8 @@ async function processRepo(params) {
|
|
|
9847
10211
|
if (!instructions.trim()) {
|
|
9848
10212
|
throw new Error("Generated instructions were empty");
|
|
9849
10213
|
}
|
|
9850
|
-
const instructionsPath =
|
|
9851
|
-
await ensureDir(
|
|
10214
|
+
const instructionsPath = path10.join(repoPath, ".github", "copilot-instructions.md");
|
|
10215
|
+
await ensureDir(path10.dirname(instructionsPath));
|
|
9852
10216
|
const { wrote, reason } = await safeWriteFile(instructionsPath, instructions, true);
|
|
9853
10217
|
if (!wrote) {
|
|
9854
10218
|
throw new Error(
|
|
@@ -9856,7 +10220,7 @@ async function processRepo(params) {
|
|
|
9856
10220
|
);
|
|
9857
10221
|
}
|
|
9858
10222
|
progress?.update(`${label}: Committing...`);
|
|
9859
|
-
await commitAll(repoPath, "chore: add
|
|
10223
|
+
await commitAll(repoPath, "chore: add instructions via AgentRC");
|
|
9860
10224
|
progress?.update(`${label}: Pushing...`);
|
|
9861
10225
|
await pushBranch(repoPath, branch, token, provider);
|
|
9862
10226
|
progress?.update(`${label}: Creating PR...`);
|
|
@@ -9892,7 +10256,7 @@ async function processGitHubRepo(options) {
|
|
|
9892
10256
|
token,
|
|
9893
10257
|
owner: repo.owner,
|
|
9894
10258
|
repo: repo.name,
|
|
9895
|
-
title: "\u{1F916} Add
|
|
10259
|
+
title: "\u{1F916} Add instructions via AgentRC",
|
|
9896
10260
|
body: buildInstructionsPrBody(),
|
|
9897
10261
|
head: branchName,
|
|
9898
10262
|
base: repo.defaultBranch
|
|
@@ -9924,7 +10288,7 @@ async function processAzureRepo(options) {
|
|
|
9924
10288
|
project: repo.project,
|
|
9925
10289
|
repoId: repo.id,
|
|
9926
10290
|
repoName: repo.name,
|
|
9927
|
-
title: "\u{1F916} Add
|
|
10291
|
+
title: "\u{1F916} Add instructions via AgentRC",
|
|
9928
10292
|
body: buildInstructionsPrBody(),
|
|
9929
10293
|
sourceBranch: branchName,
|
|
9930
10294
|
targetBranch: repo.defaultBranch
|
|
@@ -9985,7 +10349,7 @@ async function processBatchReadinessRepo(options) {
|
|
|
9985
10349
|
try {
|
|
9986
10350
|
progress(`Cloning ${repo.fullName}...`);
|
|
9987
10351
|
const authedUrl = buildAuthedUrl(repo.cloneUrl, token, "github");
|
|
9988
|
-
await ensureDir(
|
|
10352
|
+
await ensureDir(path10.dirname(repoDir));
|
|
9989
10353
|
await cloneRepo(authedUrl, repoDir, { shallow: true });
|
|
9990
10354
|
await setRemoteUrl(repoDir, repo.cloneUrl).catch(() => {
|
|
9991
10355
|
});
|
|
@@ -11035,15 +11399,11 @@ import { render as render2 } from "ink";
|
|
|
11035
11399
|
// src/ui/BatchReadinessTui.tsx
|
|
11036
11400
|
import fs8 from "fs/promises";
|
|
11037
11401
|
import os from "os";
|
|
11038
|
-
import
|
|
11402
|
+
import path11 from "path";
|
|
11039
11403
|
|
|
11040
11404
|
// packages/core/src/services/visualReport.ts
|
|
11041
11405
|
function generateVisualReport(options) {
|
|
11042
|
-
const {
|
|
11043
|
-
reports,
|
|
11044
|
-
title = "AI Readiness Report",
|
|
11045
|
-
generatedAt = (/* @__PURE__ */ new Date()).toISOString()
|
|
11046
|
-
} = options;
|
|
11406
|
+
const { reports, title = "Readiness Report", generatedAt = (/* @__PURE__ */ new Date()).toISOString() } = options;
|
|
11047
11407
|
const successfulReports = reports.filter((r) => !r.error);
|
|
11048
11408
|
const failedReports = reports.filter((r) => r.error);
|
|
11049
11409
|
const totalRepos = reports.length;
|
|
@@ -11612,7 +11972,7 @@ function generateVisualReport(options) {
|
|
|
11612
11972
|
` : ""}
|
|
11613
11973
|
|
|
11614
11974
|
<div class="footer">
|
|
11615
|
-
<p>Generated with <a href="https://github.com/microsoft/agentrc">AgentRC</a> ·
|
|
11975
|
+
<p>Generated with <a href="https://github.com/microsoft/agentrc">AgentRC</a> · Readiness Tool</p>
|
|
11616
11976
|
</div>
|
|
11617
11977
|
</div>
|
|
11618
11978
|
<script>
|
|
@@ -12065,7 +12425,7 @@ function BatchReadinessTui({ token, outputPath, policies }) {
|
|
|
12065
12425
|
setStatus("processing");
|
|
12066
12426
|
const selectedRepos = Array.from(selectedRepoIndices).map((i) => repos[i]);
|
|
12067
12427
|
const results2 = [];
|
|
12068
|
-
const tmpDir =
|
|
12428
|
+
const tmpDir = path11.join(os.tmpdir(), `agentrc-batch-readiness-${Date.now()}`);
|
|
12069
12429
|
try {
|
|
12070
12430
|
await ensureDir(tmpDir);
|
|
12071
12431
|
for (let i = 0; i < selectedRepos.length; i++) {
|
|
@@ -12099,10 +12459,10 @@ function BatchReadinessTui({ token, outputPath, policies }) {
|
|
|
12099
12459
|
},
|
|
12100
12460
|
error: r.error
|
|
12101
12461
|
})),
|
|
12102
|
-
title: "Batch
|
|
12462
|
+
title: "Batch Readiness Report",
|
|
12103
12463
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
12104
12464
|
});
|
|
12105
|
-
const finalOutputPath = outputPath ??
|
|
12465
|
+
const finalOutputPath = outputPath ?? path11.join(process.cwd(), "batch-readiness-report.html");
|
|
12106
12466
|
const { wrote, reason } = await safeWriteFile(finalOutputPath, html, true);
|
|
12107
12467
|
if (!wrote) throw new Error(reason === "symlink" ? "Path is a symlink" : "Write failed");
|
|
12108
12468
|
setStatus("complete");
|
|
@@ -12284,7 +12644,7 @@ async function batchReadinessCommand(options) {
|
|
|
12284
12644
|
}
|
|
12285
12645
|
|
|
12286
12646
|
// src/commands/eval.ts
|
|
12287
|
-
import
|
|
12647
|
+
import path13 from "path";
|
|
12288
12648
|
|
|
12289
12649
|
// packages/core/src/services/evalScaffold.ts
|
|
12290
12650
|
var EVAL_SCAFFOLD_TIMEOUT_MS = 6e5;
|
|
@@ -12306,7 +12666,7 @@ async function generateEvalScaffold(options) {
|
|
|
12306
12666
|
streaming: true,
|
|
12307
12667
|
workingDirectory: repoPath,
|
|
12308
12668
|
systemMessage: {
|
|
12309
|
-
content: "
|
|
12669
|
+
content: "Generate challenging implementation planning tasks for this repository. Each task should require cross-cutting changes across multiple files. Tasks can range from specific feature additions to broader refactoring plans. Avoid trivial tasks or pure Q&A questions. Output ONLY JSON with keys: instructionFile, cases (array of {id,prompt,expectation})."
|
|
12310
12670
|
},
|
|
12311
12671
|
infiniteSessions: { enabled: false }
|
|
12312
12672
|
});
|
|
@@ -12348,32 +12708,37 @@ async function generateEvalScaffold(options) {
|
|
|
12348
12708
|
'For areas with a workspace (workingDirectory), include a "workingDirectory" field set to that workspace path so evals run scoped to the correct folder.'
|
|
12349
12709
|
].join("\n") : "";
|
|
12350
12710
|
const prompt = [
|
|
12351
|
-
`Analyze this repository and generate ${count}
|
|
12711
|
+
`Analyze this repository and generate ${count} implementation planning tasks.`,
|
|
12712
|
+
"",
|
|
12713
|
+
"Each task should represent a REAL developer request \u2014 the kind of thing someone would actually type into a coding agent.",
|
|
12714
|
+
"Tasks should require understanding MULTIPLE files and cross-cutting concerns.",
|
|
12352
12715
|
"",
|
|
12353
|
-
"
|
|
12354
|
-
"
|
|
12355
|
-
"Do NOT
|
|
12716
|
+
"PROMPT RULES (the prompt field is what the eval agent receives):",
|
|
12717
|
+
"- Write prompts as a developer would naturally phrase them \u2014 short, direct requests.",
|
|
12718
|
+
"- Do NOT describe expected output format or what the response should include.",
|
|
12719
|
+
"- Do NOT add meta-instructions like 'The output should show...' or 'Include in your plan...' or 'Your plan should cover...'",
|
|
12720
|
+
"- Do NOT embed implementation details in the prompt (pillar names, levels, specific output formats) \u2014 those belong in the expectation.",
|
|
12721
|
+
"- Good: 'Add a --dry-run flag to the generate command'",
|
|
12722
|
+
"- Good: 'Add a readiness criterion for CI/CD detection'",
|
|
12723
|
+
"- Bad: 'Add a --dry-run flag that previews what files would be created or modified. The output should show file paths and indicate whether each would be created, updated, or skipped.'",
|
|
12724
|
+
"- Bad: 'Add a readiness criterion that checks for CI/CD and classifies it under a new deployment pillar at level 3'",
|
|
12356
12725
|
"",
|
|
12357
|
-
"
|
|
12358
|
-
"-
|
|
12359
|
-
"-
|
|
12360
|
-
"-
|
|
12361
|
-
"-
|
|
12362
|
-
"-
|
|
12363
|
-
"- Questions that require understanding the type system, generics, or shared interfaces across module boundaries",
|
|
12726
|
+
"EXPECTATION RULES (the expectation field is checked by an LLM judge \u2014 be extremely specific):",
|
|
12727
|
+
"- MUST cite exact file paths (e.g., 'packages/core/src/services/generator.ts')",
|
|
12728
|
+
"- MUST cite exact function and type names (e.g., 'generateCopilotInstructions()', 'CommandResult<T>')",
|
|
12729
|
+
"- MUST cite specific CLI commands for verification (e.g., 'npm run typecheck', 'npm test')",
|
|
12730
|
+
"- MUST name the specific test file to update (e.g., 'src/services/__tests__/generator.test.ts')",
|
|
12731
|
+
"- Generic expectations like 'identify the generator service' are USELESS \u2014 name the actual function and file.",
|
|
12364
12732
|
"",
|
|
12365
|
-
"Bad
|
|
12366
|
-
"- 'What does this project do?' (
|
|
12367
|
-
"- '
|
|
12368
|
-
"-
|
|
12369
|
-
"- Any question answerable by reading one file or searching for one keyword",
|
|
12733
|
+
"Bad task examples (avoid these):",
|
|
12734
|
+
"- 'What does this project do?' (pure Q&A, not a planning task)",
|
|
12735
|
+
"- 'Explain the architecture' (no implementation expected)",
|
|
12736
|
+
"- Tasks with prescriptive output instructions baked into the prompt",
|
|
12370
12737
|
"",
|
|
12371
|
-
"
|
|
12372
|
-
"If this is a monorepo
|
|
12373
|
-
"Ensure cases cover cross-cutting concerns: data flow, error propagation, configuration impact, implicit coupling, architectural invariants.",
|
|
12374
|
-
"Include a systemMessage that keeps answers scoped to this repository (avoid generic Copilot CLI details unless asked).",
|
|
12738
|
+
"Do NOT generate a systemMessage \u2014 the default is used automatically.",
|
|
12739
|
+
"If this is a monorepo, generate tasks that involve cross-app dependencies and shared libraries.",
|
|
12375
12740
|
"Return JSON ONLY (no markdown, no commentary) in this schema:",
|
|
12376
|
-
'{\n "instructionFile": ".github/copilot-instructions.md",\n "
|
|
12741
|
+
options.areas?.length ? '{\n "instructionFile": ".github/copilot-instructions.md",\n "cases": [\n {"id": "case-1", "prompt": "...", "expectation": "...", "area": "optional-area-name"}\n ]\n}' : '{\n "instructionFile": ".github/copilot-instructions.md",\n "cases": [\n {"id": "case-1", "prompt": "...", "expectation": "..."}\n ]\n}',
|
|
12377
12742
|
areaContext
|
|
12378
12743
|
].join("\n");
|
|
12379
12744
|
progress("Analyzing codebase...");
|
|
@@ -12419,7 +12784,8 @@ async function generateEvalScaffold(options) {
|
|
|
12419
12784
|
}
|
|
12420
12785
|
throw error;
|
|
12421
12786
|
}
|
|
12422
|
-
const
|
|
12787
|
+
const hasAreas = Boolean(options.areas?.length);
|
|
12788
|
+
const normalized = normalizeEvalConfig(parsed, count, hasAreas);
|
|
12423
12789
|
return normalized;
|
|
12424
12790
|
} finally {
|
|
12425
12791
|
await client.stop();
|
|
@@ -12443,41 +12809,47 @@ function parseEvalConfig(raw) {
|
|
|
12443
12809
|
}
|
|
12444
12810
|
return parsed;
|
|
12445
12811
|
}
|
|
12446
|
-
function normalizeEvalConfig(parsed, count) {
|
|
12812
|
+
function normalizeEvalConfig(parsed, count, hasAreas = true) {
|
|
12447
12813
|
const cases = (parsed.cases ?? []).slice(0, count).map((entry, index) => {
|
|
12448
12814
|
const id = typeof entry.id === "string" && entry.id.trim() ? entry.id : `case-${index + 1}`;
|
|
12449
12815
|
return {
|
|
12450
12816
|
id,
|
|
12451
12817
|
prompt: String(entry.prompt ?? "").trim(),
|
|
12452
|
-
expectation:
|
|
12453
|
-
|
|
12818
|
+
expectation: normalizeExpectation(entry.expectation),
|
|
12819
|
+
...hasAreas && typeof entry.area === "string" && entry.area.trim() ? { area: entry.area.trim() } : {},
|
|
12454
12820
|
workingDirectory: typeof entry.workingDirectory === "string" && entry.workingDirectory.trim() ? entry.workingDirectory.trim() : void 0
|
|
12455
12821
|
};
|
|
12456
12822
|
});
|
|
12457
12823
|
if (!cases.length) {
|
|
12458
12824
|
throw new Error("Eval scaffold JSON did not include any usable cases.");
|
|
12459
12825
|
}
|
|
12460
|
-
const defaultSystemMessage = "You are answering questions about this repository. Use tools to inspect the repo and cite its files. Avoid generic Copilot CLI details unless the prompt explicitly asks for them.";
|
|
12461
12826
|
return {
|
|
12462
12827
|
instructionFile: parsed.instructionFile ?? ".github/copilot-instructions.md",
|
|
12463
|
-
systemMessage: parsed.systemMessage
|
|
12828
|
+
...parsed.systemMessage ? { systemMessage: parsed.systemMessage } : {},
|
|
12464
12829
|
cases
|
|
12465
12830
|
};
|
|
12466
12831
|
}
|
|
12832
|
+
function normalizeExpectation(value) {
|
|
12833
|
+
if (Array.isArray(value)) {
|
|
12834
|
+
const items = value.map((s) => String(s).trim()).filter(Boolean);
|
|
12835
|
+
return items.length === 1 ? items[0] : items;
|
|
12836
|
+
}
|
|
12837
|
+
return String(value ?? "").trim();
|
|
12838
|
+
}
|
|
12467
12839
|
|
|
12468
12840
|
// packages/core/src/services/evaluator.ts
|
|
12469
12841
|
import fs9 from "fs/promises";
|
|
12470
|
-
import
|
|
12471
|
-
var DEFAULT_SYSTEM_MESSAGE = "
|
|
12842
|
+
import path12 from "path";
|
|
12843
|
+
var DEFAULT_SYSTEM_MESSAGE = "Research using read-only tools and make a plan for the given task, including concrete implementation steps and verification steps.";
|
|
12472
12844
|
async function runEval(options) {
|
|
12473
12845
|
const config = await loadConfig(options.configPath);
|
|
12474
12846
|
const instructionFile = config.instructionFile ?? ".github/copilot-instructions.md";
|
|
12475
|
-
const instructionPath =
|
|
12847
|
+
const instructionPath = path12.resolve(options.repoPath, instructionFile);
|
|
12476
12848
|
const instructionText = await readOptionalFile(instructionPath);
|
|
12477
12849
|
const baseSystemMessage = config.systemMessage ?? DEFAULT_SYSTEM_MESSAGE;
|
|
12478
12850
|
const progress = options.onProgress ?? (() => {
|
|
12479
12851
|
});
|
|
12480
|
-
const defaultOutputPath =
|
|
12852
|
+
const defaultOutputPath = path12.resolve(
|
|
12481
12853
|
options.repoPath,
|
|
12482
12854
|
".agentrc",
|
|
12483
12855
|
"evals",
|
|
@@ -12497,9 +12869,9 @@ async function runEval(options) {
|
|
|
12497
12869
|
const caseStartedAt = Date.now();
|
|
12498
12870
|
let caseWorkingDir;
|
|
12499
12871
|
if (testCase.workingDirectory) {
|
|
12500
|
-
const resolved =
|
|
12501
|
-
const root =
|
|
12502
|
-
if (resolved !== root && !resolved.startsWith(root +
|
|
12872
|
+
const resolved = path12.resolve(options.repoPath, testCase.workingDirectory);
|
|
12873
|
+
const root = path12.resolve(options.repoPath);
|
|
12874
|
+
if (resolved !== root && !resolved.startsWith(root + path12.sep)) {
|
|
12503
12875
|
throw new Error(
|
|
12504
12876
|
`Invalid workingDirectory "${testCase.workingDirectory}": escapes repo boundary`
|
|
12505
12877
|
);
|
|
@@ -12571,7 +12943,7 @@ async function runEval(options) {
|
|
|
12571
12943
|
};
|
|
12572
12944
|
let viewerPath;
|
|
12573
12945
|
if (outputPath) {
|
|
12574
|
-
await fs9.mkdir(
|
|
12946
|
+
await fs9.mkdir(path12.dirname(outputPath), { recursive: true });
|
|
12575
12947
|
await safeWriteFile(outputPath, JSON.stringify(output, null, 2), true);
|
|
12576
12948
|
viewerPath = buildViewerPath(outputPath);
|
|
12577
12949
|
await safeWriteFile(viewerPath, buildTrajectoryViewerHtml(output), true);
|
|
@@ -12632,10 +13004,11 @@ async function judge(client, options) {
|
|
|
12632
13004
|
if (delta) content += delta;
|
|
12633
13005
|
}
|
|
12634
13006
|
});
|
|
13007
|
+
const expectationText = Array.isArray(options.expectation) ? options.expectation.join("\n") : options.expectation;
|
|
12635
13008
|
const prompt = [
|
|
12636
13009
|
"Evaluate which response best matches the expectation.",
|
|
12637
13010
|
"",
|
|
12638
|
-
`Expectation: ${
|
|
13011
|
+
`Expectation: ${expectationText}`,
|
|
12639
13012
|
"",
|
|
12640
13013
|
"Response A (without custom instructions):",
|
|
12641
13014
|
options.withoutInstructions,
|
|
@@ -12679,7 +13052,7 @@ function parseJudge(content) {
|
|
|
12679
13052
|
}
|
|
12680
13053
|
async function loadConfig(configPath) {
|
|
12681
13054
|
const raw = await fs9.readFile(configPath, "utf8");
|
|
12682
|
-
const parsed = JSON.parse(raw);
|
|
13055
|
+
const parsed = JSON.parse(stripJsonComments(raw));
|
|
12683
13056
|
if (!parsed || !Array.isArray(parsed.cases)) {
|
|
12684
13057
|
throw new Error("Eval config must have a 'cases' array.");
|
|
12685
13058
|
}
|
|
@@ -12885,7 +13258,7 @@ function formatTokenUsage(usage) {
|
|
|
12885
13258
|
function resolveOutputPath(repoPath, override, configValue) {
|
|
12886
13259
|
const chosen = override ?? configValue;
|
|
12887
13260
|
if (!chosen) return void 0;
|
|
12888
|
-
return
|
|
13261
|
+
return path12.isAbsolute(chosen) ? chosen : path12.resolve(repoPath, chosen);
|
|
12889
13262
|
}
|
|
12890
13263
|
function buildViewerPath(outputPath) {
|
|
12891
13264
|
if (outputPath.endsWith(".json")) {
|
|
@@ -13240,7 +13613,7 @@ function sanitizeValue(value, depth) {
|
|
|
13240
13613
|
|
|
13241
13614
|
// src/commands/eval.ts
|
|
13242
13615
|
async function evalCommand(configPathArg, options) {
|
|
13243
|
-
const repoPath =
|
|
13616
|
+
const repoPath = path13.resolve(options.repo ?? process.cwd());
|
|
13244
13617
|
if (options.listModels) {
|
|
13245
13618
|
try {
|
|
13246
13619
|
const models = await listCopilotModels();
|
|
@@ -13276,7 +13649,7 @@ async function evalCommand(configPathArg, options) {
|
|
|
13276
13649
|
return;
|
|
13277
13650
|
}
|
|
13278
13651
|
if (options.init) {
|
|
13279
|
-
const outputPath =
|
|
13652
|
+
const outputPath = path13.join(repoPath, "agentrc.eval.json");
|
|
13280
13653
|
const desiredCount = Math.max(1, Number.parseInt(options.count ?? "5", 10) || 5);
|
|
13281
13654
|
try {
|
|
13282
13655
|
const progress = createProgressReporter(!shouldLog(options));
|
|
@@ -13318,7 +13691,7 @@ async function evalCommand(configPathArg, options) {
|
|
|
13318
13691
|
}
|
|
13319
13692
|
return;
|
|
13320
13693
|
}
|
|
13321
|
-
const configPath =
|
|
13694
|
+
const configPath = path13.resolve(configPathArg ?? path13.join(repoPath, "agentrc.eval.json"));
|
|
13322
13695
|
try {
|
|
13323
13696
|
const progress = createProgressReporter(!shouldLog(options));
|
|
13324
13697
|
const { summary, results, viewerPath } = await runEval({
|
|
@@ -13365,32 +13738,43 @@ async function evalCommand(configPathArg, options) {
|
|
|
13365
13738
|
}
|
|
13366
13739
|
|
|
13367
13740
|
// src/commands/generate.ts
|
|
13368
|
-
import
|
|
13741
|
+
import path16 from "path";
|
|
13369
13742
|
|
|
13370
13743
|
// packages/core/src/services/generator.ts
|
|
13371
|
-
import
|
|
13744
|
+
import path14 from "path";
|
|
13745
|
+
async function writeOrPreview(filePath, content, opts) {
|
|
13746
|
+
const relPath = path14.relative(process.cwd(), filePath);
|
|
13747
|
+
if (opts.dryRun) {
|
|
13748
|
+
const wouldWrite = await canSafeWrite(filePath, opts.force);
|
|
13749
|
+
return {
|
|
13750
|
+
path: relPath,
|
|
13751
|
+
action: wouldWrite ? "wrote" : "skipped",
|
|
13752
|
+
bytes: Buffer.byteLength(content, "utf8")
|
|
13753
|
+
};
|
|
13754
|
+
}
|
|
13755
|
+
await ensureDir(path14.dirname(filePath));
|
|
13756
|
+
const { wrote } = await safeWriteFile(filePath, content, opts.force);
|
|
13757
|
+
return { path: relPath, action: wrote ? "wrote" : "skipped" };
|
|
13758
|
+
}
|
|
13372
13759
|
async function generateConfigs(options) {
|
|
13373
|
-
const { repoPath, analysis, selections, force } = options;
|
|
13760
|
+
const { repoPath, analysis, selections, force, dryRun } = options;
|
|
13374
13761
|
const files = [];
|
|
13375
13762
|
if (selections.includes("mcp")) {
|
|
13376
|
-
|
|
13377
|
-
|
|
13378
|
-
|
|
13379
|
-
|
|
13380
|
-
|
|
13381
|
-
|
|
13382
|
-
action: wrote ? "wrote" : "skipped"
|
|
13383
|
-
});
|
|
13763
|
+
files.push(
|
|
13764
|
+
await writeOrPreview(path14.join(repoPath, ".vscode", "mcp.json"), renderMcp(), {
|
|
13765
|
+
dryRun,
|
|
13766
|
+
force
|
|
13767
|
+
})
|
|
13768
|
+
);
|
|
13384
13769
|
}
|
|
13385
13770
|
if (selections.includes("vscode")) {
|
|
13386
|
-
|
|
13387
|
-
|
|
13388
|
-
|
|
13389
|
-
|
|
13390
|
-
|
|
13391
|
-
|
|
13392
|
-
|
|
13393
|
-
});
|
|
13771
|
+
files.push(
|
|
13772
|
+
await writeOrPreview(
|
|
13773
|
+
path14.join(repoPath, ".vscode", "settings.json"),
|
|
13774
|
+
renderVscodeSettings(analysis),
|
|
13775
|
+
{ dryRun, force }
|
|
13776
|
+
)
|
|
13777
|
+
);
|
|
13394
13778
|
}
|
|
13395
13779
|
return { files };
|
|
13396
13780
|
}
|
|
@@ -13439,16 +13823,16 @@ function renderVscodeSettings(analysis) {
|
|
|
13439
13823
|
}
|
|
13440
13824
|
|
|
13441
13825
|
// src/commands/instructions.ts
|
|
13442
|
-
import
|
|
13826
|
+
import path15 from "path";
|
|
13443
13827
|
function skipReason(action) {
|
|
13444
13828
|
if (action === "symlink") return "symlink";
|
|
13445
13829
|
if (action === "empty") return "empty content";
|
|
13446
13830
|
return "exists, use --force";
|
|
13447
13831
|
}
|
|
13448
13832
|
async function instructionsCommand(options) {
|
|
13449
|
-
const repoPath =
|
|
13450
|
-
const outputPath =
|
|
13451
|
-
options.output ??
|
|
13833
|
+
const repoPath = path15.resolve(options.repo ?? process.cwd());
|
|
13834
|
+
const outputPath = path15.resolve(
|
|
13835
|
+
options.output ?? path15.join(repoPath, ".github", "copilot-instructions.md")
|
|
13452
13836
|
);
|
|
13453
13837
|
const progress = createProgressReporter(!shouldLog(options));
|
|
13454
13838
|
const wantAreas = options.areas || options.areasOnly || options.area;
|
|
@@ -13471,6 +13855,7 @@ async function instructionsCommand(options) {
|
|
|
13471
13855
|
return;
|
|
13472
13856
|
}
|
|
13473
13857
|
try {
|
|
13858
|
+
const dryRunFiles = [];
|
|
13474
13859
|
if (!options.areasOnly && !options.area) {
|
|
13475
13860
|
if (strategy === "nested") {
|
|
13476
13861
|
try {
|
|
@@ -13482,25 +13867,57 @@ async function instructionsCommand(options) {
|
|
|
13482
13867
|
detailDir,
|
|
13483
13868
|
claudeMd
|
|
13484
13869
|
});
|
|
13485
|
-
|
|
13486
|
-
|
|
13487
|
-
|
|
13488
|
-
|
|
13489
|
-
|
|
13490
|
-
|
|
13491
|
-
|
|
13870
|
+
if (options.dryRun) {
|
|
13871
|
+
const dryFiles = [
|
|
13872
|
+
{ path: nestedResult.hub.relativePath, content: nestedResult.hub.content },
|
|
13873
|
+
...nestedResult.details.map((d) => ({ path: d.relativePath, content: d.content })),
|
|
13874
|
+
...nestedResult.claudeMd ? [
|
|
13875
|
+
{
|
|
13876
|
+
path: nestedResult.claudeMd.relativePath,
|
|
13877
|
+
content: nestedResult.claudeMd.content
|
|
13878
|
+
}
|
|
13879
|
+
] : []
|
|
13880
|
+
];
|
|
13881
|
+
for (const file of dryFiles) {
|
|
13882
|
+
const relPath = path15.relative(process.cwd(), path15.join(repoPath, file.path));
|
|
13883
|
+
if (shouldLog(options)) {
|
|
13884
|
+
progress.update(
|
|
13885
|
+
`[dry-run] Would write ${relPath} (${Buffer.byteLength(file.content, "utf8")} bytes)`
|
|
13886
|
+
);
|
|
13887
|
+
}
|
|
13888
|
+
}
|
|
13889
|
+
if (options.json) {
|
|
13890
|
+
dryRunFiles.push(
|
|
13891
|
+
...dryFiles.map((f) => ({
|
|
13892
|
+
path: f.path,
|
|
13893
|
+
bytes: Buffer.byteLength(f.content, "utf8")
|
|
13894
|
+
}))
|
|
13895
|
+
);
|
|
13896
|
+
}
|
|
13897
|
+
for (const warning of nestedResult.warnings) {
|
|
13898
|
+
if (shouldLog(options)) progress.update(`Warning: ${warning}`);
|
|
13899
|
+
}
|
|
13900
|
+
} else {
|
|
13901
|
+
const actions = await writeNestedInstructions(repoPath, nestedResult, options.force);
|
|
13902
|
+
for (const action of actions) {
|
|
13903
|
+
const relPath = path15.relative(process.cwd(), action.path);
|
|
13904
|
+
if (action.action === "wrote") {
|
|
13905
|
+
if (shouldLog(options)) progress.succeed(`Wrote ${relPath}`);
|
|
13906
|
+
} else if (shouldLog(options)) {
|
|
13907
|
+
progress.update(`Skipped ${relPath} (${skipReason(action.action)})`);
|
|
13908
|
+
}
|
|
13909
|
+
}
|
|
13910
|
+
for (const warning of nestedResult.warnings) {
|
|
13911
|
+
if (shouldLog(options)) progress.update(`Warning: ${warning}`);
|
|
13912
|
+
}
|
|
13913
|
+
if (options.json) {
|
|
13914
|
+
const result = {
|
|
13915
|
+
ok: true,
|
|
13916
|
+
status: "success",
|
|
13917
|
+
data: { files: actions }
|
|
13918
|
+
};
|
|
13919
|
+
outputResult(result, true);
|
|
13492
13920
|
}
|
|
13493
|
-
}
|
|
13494
|
-
for (const warning of nestedResult.warnings) {
|
|
13495
|
-
if (shouldLog(options)) progress.update(`Warning: ${warning}`);
|
|
13496
|
-
}
|
|
13497
|
-
if (options.json) {
|
|
13498
|
-
const result = {
|
|
13499
|
-
ok: true,
|
|
13500
|
-
status: "success",
|
|
13501
|
-
data: { files: actions }
|
|
13502
|
-
};
|
|
13503
|
-
outputResult(result, true);
|
|
13504
13921
|
}
|
|
13505
13922
|
} catch (error) {
|
|
13506
13923
|
const msg = "Failed to generate nested instructions. " + (error instanceof Error ? error.message : String(error));
|
|
@@ -13525,36 +13942,48 @@ async function instructionsCommand(options) {
|
|
|
13525
13942
|
return;
|
|
13526
13943
|
}
|
|
13527
13944
|
if (content) {
|
|
13528
|
-
|
|
13529
|
-
|
|
13530
|
-
outputPath
|
|
13531
|
-
content,
|
|
13532
|
-
|
|
13533
|
-
|
|
13534
|
-
|
|
13535
|
-
const relPath = path14.relative(process.cwd(), outputPath);
|
|
13536
|
-
const why = reason === "symlink" ? "path is a symlink" : "file exists (use --force)";
|
|
13945
|
+
if (options.dryRun) {
|
|
13946
|
+
const relPath = path15.relative(repoPath, outputPath);
|
|
13947
|
+
const displayPath = path15.relative(process.cwd(), outputPath);
|
|
13948
|
+
const byteCount = Buffer.byteLength(content, "utf8");
|
|
13949
|
+
if (shouldLog(options)) {
|
|
13950
|
+
progress.update(`[dry-run] Would write ${displayPath} (${byteCount} bytes)`);
|
|
13951
|
+
}
|
|
13537
13952
|
if (options.json) {
|
|
13538
|
-
|
|
13539
|
-
ok: true,
|
|
13540
|
-
status: "noop",
|
|
13541
|
-
data: { outputPath, skipped: true, reason: why }
|
|
13542
|
-
};
|
|
13543
|
-
outputResult(result, true);
|
|
13544
|
-
} else if (shouldLog(options)) {
|
|
13545
|
-
progress.update(`Skipped ${relPath}: ${why}`);
|
|
13953
|
+
dryRunFiles.push({ path: relPath, bytes: byteCount });
|
|
13546
13954
|
}
|
|
13547
13955
|
} else {
|
|
13548
|
-
|
|
13549
|
-
|
|
13550
|
-
|
|
13551
|
-
|
|
13552
|
-
|
|
13553
|
-
|
|
13554
|
-
|
|
13555
|
-
|
|
13556
|
-
|
|
13557
|
-
|
|
13956
|
+
await ensureDir(path15.dirname(outputPath));
|
|
13957
|
+
const { wrote, reason } = await safeWriteFile(
|
|
13958
|
+
outputPath,
|
|
13959
|
+
content,
|
|
13960
|
+
Boolean(options.force)
|
|
13961
|
+
);
|
|
13962
|
+
if (!wrote) {
|
|
13963
|
+
const relPath = path15.relative(process.cwd(), outputPath);
|
|
13964
|
+
const why = reason === "symlink" ? "path is a symlink" : "file exists (use --force)";
|
|
13965
|
+
if (options.json) {
|
|
13966
|
+
const result = {
|
|
13967
|
+
ok: true,
|
|
13968
|
+
status: "noop",
|
|
13969
|
+
data: { outputPath, skipped: true, reason: why }
|
|
13970
|
+
};
|
|
13971
|
+
outputResult(result, true);
|
|
13972
|
+
} else if (shouldLog(options)) {
|
|
13973
|
+
progress.update(`Skipped ${relPath}: ${why}`);
|
|
13974
|
+
}
|
|
13975
|
+
} else {
|
|
13976
|
+
const byteCount = Buffer.byteLength(content, "utf8");
|
|
13977
|
+
if (options.json) {
|
|
13978
|
+
const result = {
|
|
13979
|
+
ok: true,
|
|
13980
|
+
status: "success",
|
|
13981
|
+
data: { outputPath, model: options.model ?? "default", byteCount }
|
|
13982
|
+
};
|
|
13983
|
+
outputResult(result, true);
|
|
13984
|
+
} else if (shouldLog(options)) {
|
|
13985
|
+
progress.succeed(`Updated ${path15.relative(process.cwd(), outputPath)}`);
|
|
13986
|
+
}
|
|
13558
13987
|
}
|
|
13559
13988
|
}
|
|
13560
13989
|
}
|
|
@@ -13588,7 +14017,7 @@ async function instructionsCommand(options) {
|
|
|
13588
14017
|
return;
|
|
13589
14018
|
}
|
|
13590
14019
|
if (shouldLog(options)) {
|
|
13591
|
-
progress.update(`Generating
|
|
14020
|
+
progress.update(`Generating instructions for ${targetAreas.length} area(s)...`);
|
|
13592
14021
|
}
|
|
13593
14022
|
for (const area of targetAreas) {
|
|
13594
14023
|
try {
|
|
@@ -13608,17 +14037,46 @@ async function instructionsCommand(options) {
|
|
|
13608
14037
|
detailDir,
|
|
13609
14038
|
claudeMd
|
|
13610
14039
|
});
|
|
13611
|
-
|
|
13612
|
-
|
|
13613
|
-
|
|
13614
|
-
|
|
13615
|
-
|
|
13616
|
-
|
|
13617
|
-
|
|
14040
|
+
if (options.dryRun) {
|
|
14041
|
+
const dryFiles = [
|
|
14042
|
+
{ path: nestedResult.hub.relativePath, content: nestedResult.hub.content },
|
|
14043
|
+
...nestedResult.details.map((d) => ({ path: d.relativePath, content: d.content })),
|
|
14044
|
+
...nestedResult.claudeMd ? [
|
|
14045
|
+
{
|
|
14046
|
+
path: nestedResult.claudeMd.relativePath,
|
|
14047
|
+
content: nestedResult.claudeMd.content
|
|
14048
|
+
}
|
|
14049
|
+
] : []
|
|
14050
|
+
];
|
|
14051
|
+
for (const file of dryFiles) {
|
|
14052
|
+
const relPath = path15.relative(process.cwd(), path15.join(repoPath, file.path));
|
|
14053
|
+
if (shouldLog(options)) {
|
|
14054
|
+
progress.update(
|
|
14055
|
+
`[dry-run] Would write ${relPath} (${Buffer.byteLength(file.content, "utf8")} bytes)`
|
|
14056
|
+
);
|
|
14057
|
+
}
|
|
14058
|
+
}
|
|
14059
|
+
if (options.json) {
|
|
14060
|
+
dryRunFiles.push(
|
|
14061
|
+
...dryFiles.map((f) => ({
|
|
14062
|
+
path: f.path,
|
|
14063
|
+
bytes: Buffer.byteLength(f.content, "utf8")
|
|
14064
|
+
}))
|
|
14065
|
+
);
|
|
14066
|
+
}
|
|
14067
|
+
} else {
|
|
14068
|
+
const actions = await writeNestedInstructions(repoPath, nestedResult, options.force);
|
|
14069
|
+
for (const action of actions) {
|
|
14070
|
+
const relPath = path15.relative(process.cwd(), action.path);
|
|
14071
|
+
if (action.action === "wrote") {
|
|
14072
|
+
if (shouldLog(options)) progress.succeed(`Wrote ${relPath}`);
|
|
14073
|
+
} else if (shouldLog(options)) {
|
|
14074
|
+
progress.update(`Skipped ${relPath} (${skipReason(action.action)})`);
|
|
14075
|
+
}
|
|
14076
|
+
}
|
|
14077
|
+
for (const warning of nestedResult.warnings) {
|
|
14078
|
+
if (shouldLog(options)) progress.update(`Warning: ${warning}`);
|
|
13618
14079
|
}
|
|
13619
|
-
}
|
|
13620
|
-
for (const warning of nestedResult.warnings) {
|
|
13621
|
-
if (shouldLog(options)) progress.update(`Warning: ${warning}`);
|
|
13622
14080
|
}
|
|
13623
14081
|
} else {
|
|
13624
14082
|
const body = await generateAreaInstructions({
|
|
@@ -13633,21 +14091,37 @@ async function instructionsCommand(options) {
|
|
|
13633
14091
|
}
|
|
13634
14092
|
continue;
|
|
13635
14093
|
}
|
|
13636
|
-
|
|
13637
|
-
if (result.status === "skipped") {
|
|
14094
|
+
if (options.dryRun) {
|
|
13638
14095
|
if (shouldLog(options)) {
|
|
13639
|
-
progress.update(
|
|
14096
|
+
progress.update(
|
|
14097
|
+
`[dry-run] Would write area "${area.name}" (${Buffer.byteLength(body, "utf8")} bytes)`
|
|
14098
|
+
);
|
|
14099
|
+
}
|
|
14100
|
+
if (options.json) {
|
|
14101
|
+
dryRunFiles.push({
|
|
14102
|
+
path: path15.relative(repoPath, areaInstructionPath(repoPath, area)),
|
|
14103
|
+
bytes: Buffer.byteLength(body, "utf8")
|
|
14104
|
+
});
|
|
14105
|
+
}
|
|
14106
|
+
} else {
|
|
14107
|
+
const result = await writeAreaInstruction(repoPath, area, body, options.force);
|
|
14108
|
+
if (result.status === "skipped") {
|
|
14109
|
+
if (shouldLog(options)) {
|
|
14110
|
+
progress.update(
|
|
14111
|
+
`Skipped "${area.name}" \u2014 file exists (use --force to overwrite).`
|
|
14112
|
+
);
|
|
14113
|
+
}
|
|
14114
|
+
continue;
|
|
14115
|
+
}
|
|
14116
|
+
if (result.status === "symlink") {
|
|
14117
|
+
if (shouldLog(options)) {
|
|
14118
|
+
progress.update(`Skipped "${area.name}" \u2014 path is a symlink.`);
|
|
14119
|
+
}
|
|
14120
|
+
continue;
|
|
13640
14121
|
}
|
|
13641
|
-
continue;
|
|
13642
|
-
}
|
|
13643
|
-
if (result.status === "symlink") {
|
|
13644
14122
|
if (shouldLog(options)) {
|
|
13645
|
-
progress.
|
|
14123
|
+
progress.succeed(`Wrote ${path15.relative(process.cwd(), result.filePath)}`);
|
|
13646
14124
|
}
|
|
13647
|
-
continue;
|
|
13648
|
-
}
|
|
13649
|
-
if (shouldLog(options)) {
|
|
13650
|
-
progress.succeed(`Wrote ${path14.relative(process.cwd(), result.filePath)}`);
|
|
13651
14125
|
}
|
|
13652
14126
|
}
|
|
13653
14127
|
} catch (error) {
|
|
@@ -13659,6 +14133,12 @@ async function instructionsCommand(options) {
|
|
|
13659
14133
|
}
|
|
13660
14134
|
}
|
|
13661
14135
|
}
|
|
14136
|
+
if (options.dryRun && options.json) {
|
|
14137
|
+
outputResult(
|
|
14138
|
+
{ ok: true, status: "noop", data: { dryRun: true, files: dryRunFiles } },
|
|
14139
|
+
true
|
|
14140
|
+
);
|
|
14141
|
+
}
|
|
13662
14142
|
if (!wantAreas && shouldLog(options) && !options.json) {
|
|
13663
14143
|
process.stderr.write("\nNext steps:\n");
|
|
13664
14144
|
process.stderr.write(" agentrc eval --init Scaffold evaluation test cases\n");
|
|
@@ -13675,9 +14155,21 @@ async function instructionsCommand(options) {
|
|
|
13675
14155
|
|
|
13676
14156
|
// src/commands/generate.ts
|
|
13677
14157
|
async function generateCommand(type, repoPathArg, options) {
|
|
13678
|
-
const repoPath =
|
|
14158
|
+
const repoPath = path16.resolve(repoPathArg ?? process.cwd());
|
|
13679
14159
|
if (type === "instructions" || type === "agents") {
|
|
13680
|
-
|
|
14160
|
+
if (!options.quiet) {
|
|
14161
|
+
process.stderr.write(
|
|
14162
|
+
`\u26A0 \`generate ${type}\` is deprecated \u2014 use \`agentrc instructions\` directly.
|
|
14163
|
+
`
|
|
14164
|
+
);
|
|
14165
|
+
}
|
|
14166
|
+
if (options.perApp && !options.quiet) {
|
|
14167
|
+
process.stderr.write(
|
|
14168
|
+
`\u26A0 --per-app is deprecated \u2014 use \`agentrc instructions --areas\` instead.
|
|
14169
|
+
`
|
|
14170
|
+
);
|
|
14171
|
+
}
|
|
14172
|
+
const output = type === "agents" ? path16.join(repoPath, "AGENTS.md") : void 0;
|
|
13681
14173
|
await instructionsCommand({
|
|
13682
14174
|
repo: repoPath,
|
|
13683
14175
|
output,
|
|
@@ -13686,7 +14178,8 @@ async function generateCommand(type, repoPathArg, options) {
|
|
|
13686
14178
|
json: options.json,
|
|
13687
14179
|
quiet: options.quiet,
|
|
13688
14180
|
areas: options.perApp,
|
|
13689
|
-
strategy: options.strategy
|
|
14181
|
+
strategy: options.strategy,
|
|
14182
|
+
dryRun: options.dryRun
|
|
13690
14183
|
});
|
|
13691
14184
|
return;
|
|
13692
14185
|
}
|
|
@@ -13707,7 +14200,8 @@ async function generateCommand(type, repoPathArg, options) {
|
|
|
13707
14200
|
repoPath,
|
|
13708
14201
|
analysis,
|
|
13709
14202
|
selections,
|
|
13710
|
-
force: Boolean(options.force)
|
|
14203
|
+
force: Boolean(options.force),
|
|
14204
|
+
dryRun: Boolean(options.dryRun)
|
|
13711
14205
|
});
|
|
13712
14206
|
} catch (error) {
|
|
13713
14207
|
outputError(
|
|
@@ -13721,14 +14215,16 @@ async function generateCommand(type, repoPathArg, options) {
|
|
|
13721
14215
|
const result = {
|
|
13722
14216
|
ok,
|
|
13723
14217
|
status,
|
|
13724
|
-
data: { type, files: genResult.files }
|
|
14218
|
+
data: { type, files: genResult.files, dryRun: options.dryRun }
|
|
13725
14219
|
};
|
|
13726
14220
|
outputResult(result, true);
|
|
13727
14221
|
if (!ok) process.exitCode = 1;
|
|
13728
14222
|
} else {
|
|
13729
14223
|
for (const file of genResult.files) {
|
|
13730
14224
|
if (shouldLog(options)) {
|
|
13731
|
-
|
|
14225
|
+
const prefix = options.dryRun ? file.action === "wrote" ? "[dry-run] Would write" : "[dry-run] Would skip" : file.action === "wrote" ? "Wrote" : "Skipped";
|
|
14226
|
+
const suffix = options.dryRun && file.bytes !== void 0 ? ` (${file.bytes} bytes)` : "";
|
|
14227
|
+
process.stderr.write(`${prefix} ${file.path}${suffix}
|
|
13732
14228
|
`);
|
|
13733
14229
|
}
|
|
13734
14230
|
}
|
|
@@ -13739,10 +14235,35 @@ async function generateCommand(type, repoPathArg, options) {
|
|
|
13739
14235
|
}
|
|
13740
14236
|
|
|
13741
14237
|
// src/commands/init.ts
|
|
13742
|
-
import
|
|
14238
|
+
import path18 from "path";
|
|
14239
|
+
|
|
14240
|
+
// packages/core/src/services/configScaffold.ts
|
|
14241
|
+
import path17 from "path";
|
|
14242
|
+
async function scaffoldAgentrcConfig(repoPath, areas, force = false) {
|
|
14243
|
+
const configPath = path17.join(repoPath, "agentrc.config.json");
|
|
14244
|
+
const workspaces = areas.length > 0 ? await detectWorkspaces(repoPath, areas) : [];
|
|
14245
|
+
const workspacePaths = workspaces.map((ws) => ws.path + "/");
|
|
14246
|
+
const standaloneAreas = areas.filter((a) => {
|
|
14247
|
+
if (!a.path) return true;
|
|
14248
|
+
const rel = path17.relative(repoPath, a.path).replace(/\\/gu, "/");
|
|
14249
|
+
return !workspacePaths.some((prefix) => rel.startsWith(prefix));
|
|
14250
|
+
}).map((a) => ({
|
|
14251
|
+
name: a.name,
|
|
14252
|
+
applyTo: a.applyTo,
|
|
14253
|
+
...a.description ? { description: a.description } : {}
|
|
14254
|
+
}));
|
|
14255
|
+
const agentrcConfig = {};
|
|
14256
|
+
if (workspaces.length > 0) agentrcConfig.workspaces = workspaces;
|
|
14257
|
+
if (standaloneAreas.length > 0) agentrcConfig.areas = standaloneAreas;
|
|
14258
|
+
const configContent = JSON.stringify(agentrcConfig, null, 2) + "\n";
|
|
14259
|
+
const { wrote } = await safeWriteFile(configPath, configContent, force);
|
|
14260
|
+
return { wrote, configPath };
|
|
14261
|
+
}
|
|
14262
|
+
|
|
14263
|
+
// src/commands/init.ts
|
|
13743
14264
|
import { checkbox, select } from "@inquirer/prompts";
|
|
13744
14265
|
async function initCommand(repoPathArg, options) {
|
|
13745
|
-
let repoPath =
|
|
14266
|
+
let repoPath = path18.resolve(repoPathArg ?? process.cwd());
|
|
13746
14267
|
const provider = options.provider ?? (options.github ? "github" : void 0);
|
|
13747
14268
|
if (provider && provider !== "github" && provider !== "azure") {
|
|
13748
14269
|
outputError("Invalid provider. Use github or azure.", Boolean(options.json));
|
|
@@ -13780,7 +14301,7 @@ async function initCommand(repoPathArg, options) {
|
|
|
13780
14301
|
value: repo
|
|
13781
14302
|
}))
|
|
13782
14303
|
});
|
|
13783
|
-
const cacheRoot =
|
|
14304
|
+
const cacheRoot = path18.join(process.cwd(), ".agentrc-cache");
|
|
13784
14305
|
repoPath = validateCachePath(cacheRoot, selection.owner, selection.name);
|
|
13785
14306
|
await ensureDir(repoPath);
|
|
13786
14307
|
const hasGit = await isGitRepo(repoPath);
|
|
@@ -13833,7 +14354,7 @@ async function initCommand(repoPathArg, options) {
|
|
|
13833
14354
|
value: repo
|
|
13834
14355
|
}))
|
|
13835
14356
|
});
|
|
13836
|
-
const cacheRoot =
|
|
14357
|
+
const cacheRoot = path18.join(process.cwd(), ".agentrc-cache");
|
|
13837
14358
|
repoPath = validateCachePath(
|
|
13838
14359
|
cacheRoot,
|
|
13839
14360
|
orgSelection.name,
|
|
@@ -13872,17 +14393,17 @@ async function initCommand(repoPathArg, options) {
|
|
|
13872
14393
|
});
|
|
13873
14394
|
const allFiles = [];
|
|
13874
14395
|
if (selections.includes("instructions")) {
|
|
13875
|
-
const outputPath =
|
|
13876
|
-
await ensureDir(
|
|
14396
|
+
const outputPath = path18.join(repoPath, ".github", "copilot-instructions.md");
|
|
14397
|
+
await ensureDir(path18.dirname(outputPath));
|
|
13877
14398
|
try {
|
|
13878
14399
|
const content = await generateCopilotInstructions({ repoPath, model: options.model });
|
|
13879
14400
|
const { wrote } = await safeWriteFile(outputPath, content, Boolean(options.force));
|
|
13880
14401
|
allFiles.push({
|
|
13881
|
-
path:
|
|
14402
|
+
path: path18.relative(process.cwd(), outputPath),
|
|
13882
14403
|
action: wrote ? "wrote" : "skipped"
|
|
13883
14404
|
});
|
|
13884
14405
|
if (shouldLog(options)) {
|
|
13885
|
-
const rel =
|
|
14406
|
+
const rel = path18.relative(process.cwd(), outputPath);
|
|
13886
14407
|
process.stderr.write((wrote ? `Wrote ${rel}` : `Skipped ${rel} (exists)`) + "\n");
|
|
13887
14408
|
}
|
|
13888
14409
|
} catch (error) {
|
|
@@ -13909,30 +14430,16 @@ async function initCommand(repoPathArg, options) {
|
|
|
13909
14430
|
return;
|
|
13910
14431
|
}
|
|
13911
14432
|
allFiles.push(...genResult.files);
|
|
13912
|
-
|
|
13913
|
-
const
|
|
13914
|
-
|
|
13915
|
-
|
|
13916
|
-
|
|
13917
|
-
|
|
13918
|
-
|
|
13919
|
-
|
|
13920
|
-
|
|
13921
|
-
|
|
13922
|
-
applyTo: a.applyTo,
|
|
13923
|
-
...a.description ? { description: a.description } : {}
|
|
13924
|
-
}));
|
|
13925
|
-
const agentrcConfig = {};
|
|
13926
|
-
if (workspaces.length > 0) agentrcConfig.workspaces = workspaces;
|
|
13927
|
-
if (standaloneAreas.length > 0) agentrcConfig.areas = standaloneAreas;
|
|
13928
|
-
if (agentrcConfig.workspaces || agentrcConfig.areas) {
|
|
13929
|
-
const configContent = JSON.stringify(agentrcConfig, null, 2) + "\n";
|
|
13930
|
-
const { wrote } = await safeWriteFile(configPath, configContent, Boolean(options.force));
|
|
13931
|
-
const rel = path16.relative(process.cwd(), configPath);
|
|
13932
|
-
allFiles.push({ path: rel, action: wrote ? "wrote" : "skipped" });
|
|
13933
|
-
if (shouldLog(options)) {
|
|
13934
|
-
process.stderr.write((wrote ? `Wrote ${rel}` : `Skipped ${rel} (exists)`) + "\n");
|
|
13935
|
-
}
|
|
14433
|
+
{
|
|
14434
|
+
const result = await scaffoldAgentrcConfig(
|
|
14435
|
+
repoPath,
|
|
14436
|
+
analysis.areas ?? [],
|
|
14437
|
+
Boolean(options.force)
|
|
14438
|
+
);
|
|
14439
|
+
const rel = path18.relative(process.cwd(), result.configPath);
|
|
14440
|
+
allFiles.push({ path: rel, action: result.wrote ? "wrote" : "skipped" });
|
|
14441
|
+
if (shouldLog(options)) {
|
|
14442
|
+
process.stderr.write((result.wrote ? `Wrote ${rel}` : `Skipped ${rel} (exists)`) + "\n");
|
|
13936
14443
|
}
|
|
13937
14444
|
}
|
|
13938
14445
|
if (options.json) {
|
|
@@ -13949,7 +14456,7 @@ async function initCommand(repoPathArg, options) {
|
|
|
13949
14456
|
`);
|
|
13950
14457
|
}
|
|
13951
14458
|
process.stderr.write("\nNext steps:\n");
|
|
13952
|
-
process.stderr.write(" agentrc readiness
|
|
14459
|
+
process.stderr.write(" agentrc readiness Run readiness report across 9 pillars\n");
|
|
13953
14460
|
if (analysis.areas && analysis.areas.length > 0) {
|
|
13954
14461
|
process.stderr.write(" agentrc instructions --areas Generate per-area instructions\n");
|
|
13955
14462
|
}
|
|
@@ -13958,7 +14465,7 @@ async function initCommand(repoPathArg, options) {
|
|
|
13958
14465
|
}
|
|
13959
14466
|
|
|
13960
14467
|
// src/commands/pr.ts
|
|
13961
|
-
import
|
|
14468
|
+
import path19 from "path";
|
|
13962
14469
|
var DEFAULT_PR_BRANCH = "agentrc/add-ai-config";
|
|
13963
14470
|
async function prCommand(repo, options) {
|
|
13964
14471
|
const provider = options.provider ?? "github";
|
|
@@ -13992,7 +14499,7 @@ async function prCommand(repo, options) {
|
|
|
13992
14499
|
try {
|
|
13993
14500
|
progress.update("Fetching repo info...");
|
|
13994
14501
|
const repoInfo = await getRepo(token2, organization, project, name2);
|
|
13995
|
-
const cacheRoot =
|
|
14502
|
+
const cacheRoot = path19.join(process.cwd(), ".agentrc-cache");
|
|
13996
14503
|
const repoPath = validateCachePath(cacheRoot, organization, project, name2);
|
|
13997
14504
|
await ensureDir(repoPath);
|
|
13998
14505
|
if (!await isGitRepo(repoPath)) {
|
|
@@ -14006,8 +14513,8 @@ async function prCommand(repo, options) {
|
|
|
14006
14513
|
await checkoutBranch(repoPath, branch);
|
|
14007
14514
|
progress.update("Generating instructions...");
|
|
14008
14515
|
const instructions = await generateCopilotInstructions({ repoPath, model: options.model });
|
|
14009
|
-
const instructionsPath =
|
|
14010
|
-
await ensureDir(
|
|
14516
|
+
const instructionsPath = path19.join(repoPath, ".github", "copilot-instructions.md");
|
|
14517
|
+
await ensureDir(path19.dirname(instructionsPath));
|
|
14011
14518
|
const { wrote, reason } = await safeWriteFile(instructionsPath, instructions, true);
|
|
14012
14519
|
if (!wrote) {
|
|
14013
14520
|
throw new Error(
|
|
@@ -14074,7 +14581,7 @@ async function prCommand(repo, options) {
|
|
|
14074
14581
|
try {
|
|
14075
14582
|
progress.update("Fetching repo info...");
|
|
14076
14583
|
const repoInfo = await getRepo2(token, owner, name);
|
|
14077
|
-
const cacheRoot =
|
|
14584
|
+
const cacheRoot = path19.join(process.cwd(), ".agentrc-cache");
|
|
14078
14585
|
const repoPath = validateCachePath(cacheRoot, owner, name);
|
|
14079
14586
|
await ensureDir(repoPath);
|
|
14080
14587
|
if (!await isGitRepo(repoPath)) {
|
|
@@ -14086,8 +14593,8 @@ async function prCommand(repo, options) {
|
|
|
14086
14593
|
await checkoutBranch(repoPath, branch);
|
|
14087
14594
|
progress.update("Generating instructions...");
|
|
14088
14595
|
const instructions = await generateCopilotInstructions({ repoPath, model: options.model });
|
|
14089
|
-
const instructionsPath =
|
|
14090
|
-
await ensureDir(
|
|
14596
|
+
const instructionsPath = path19.join(repoPath, ".github", "copilot-instructions.md");
|
|
14597
|
+
await ensureDir(path19.dirname(instructionsPath));
|
|
14091
14598
|
const { wrote, reason } = await safeWriteFile(instructionsPath, instructions, true);
|
|
14092
14599
|
if (!wrote) {
|
|
14093
14600
|
throw new Error(
|
|
@@ -14136,13 +14643,13 @@ async function prCommand(repo, options) {
|
|
|
14136
14643
|
}
|
|
14137
14644
|
|
|
14138
14645
|
// src/commands/readiness.ts
|
|
14139
|
-
import
|
|
14646
|
+
import path20 from "path";
|
|
14140
14647
|
import chalk3 from "chalk";
|
|
14141
14648
|
async function readinessCommand(repoPathArg, options) {
|
|
14142
|
-
const repoPath =
|
|
14143
|
-
const repoName =
|
|
14144
|
-
const resolvedOutputPath = options.output ?
|
|
14145
|
-
const outputExt = options.output ?
|
|
14649
|
+
const repoPath = path20.resolve(repoPathArg ?? process.cwd());
|
|
14650
|
+
const repoName = path20.basename(repoPath);
|
|
14651
|
+
const resolvedOutputPath = options.output ? path20.resolve(options.output) : "";
|
|
14652
|
+
const outputExt = options.output ? path20.extname(options.output).toLowerCase() : "";
|
|
14146
14653
|
let failLevelError;
|
|
14147
14654
|
let report;
|
|
14148
14655
|
try {
|
|
@@ -14201,10 +14708,10 @@ async function readinessCommand(repoPathArg, options) {
|
|
|
14201
14708
|
if (options.visual || outputExt === ".html") {
|
|
14202
14709
|
const html = generateVisualReport({
|
|
14203
14710
|
reports: [{ repo: repoName, report }],
|
|
14204
|
-
title: `
|
|
14711
|
+
title: `Readiness Report: ${repoName}`,
|
|
14205
14712
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
14206
14713
|
});
|
|
14207
|
-
const outputPath = options.output ? resolvedOutputPath :
|
|
14714
|
+
const outputPath = options.output ? resolvedOutputPath : path20.join(repoPath, "readiness-report.html");
|
|
14208
14715
|
const { wrote, reason } = await safeWriteFile(outputPath, html, Boolean(options.force));
|
|
14209
14716
|
if (!wrote) {
|
|
14210
14717
|
const why = reason === "symlink" ? "path is a symlink" : "file exists (use --force)";
|
|
@@ -14301,7 +14808,7 @@ ${label}`));
|
|
|
14301
14808
|
}
|
|
14302
14809
|
}
|
|
14303
14810
|
if (report.extras.length) {
|
|
14304
|
-
log(chalk3.bold("\
|
|
14811
|
+
log(chalk3.bold("\nReadiness extras"));
|
|
14305
14812
|
for (const extra of report.extras) {
|
|
14306
14813
|
const icon = extra.status === "pass" ? chalk3.green("\u2714") : chalk3.red("\u2716");
|
|
14307
14814
|
log(`${icon} ${extra.title}`);
|
|
@@ -14366,7 +14873,7 @@ function printAreaBreakdown(areaReports) {
|
|
|
14366
14873
|
}
|
|
14367
14874
|
function formatReadinessMarkdown(report, repoName) {
|
|
14368
14875
|
const lines = [];
|
|
14369
|
-
lines.push(`#
|
|
14876
|
+
lines.push(`# Readiness Report: ${repoName}`);
|
|
14370
14877
|
lines.push("");
|
|
14371
14878
|
lines.push(`**Level ${report.achievedLevel}** \u2014 ${levelName(report.achievedLevel)}`);
|
|
14372
14879
|
lines.push("");
|
|
@@ -14399,7 +14906,7 @@ function formatReadinessMarkdown(report, repoName) {
|
|
|
14399
14906
|
lines.push("");
|
|
14400
14907
|
}
|
|
14401
14908
|
if (report.extras.length > 0) {
|
|
14402
|
-
lines.push("##
|
|
14909
|
+
lines.push("## Readiness Extras");
|
|
14403
14910
|
lines.push("");
|
|
14404
14911
|
for (const extra of report.extras) {
|
|
14405
14912
|
const icon = extra.status === "pass" ? "\u2705" : "\u274C";
|
|
@@ -14439,26 +14946,32 @@ function formatReadinessMarkdown(report, repoName) {
|
|
|
14439
14946
|
}
|
|
14440
14947
|
|
|
14441
14948
|
// src/commands/tui.tsx
|
|
14442
|
-
import
|
|
14949
|
+
import path22 from "path";
|
|
14443
14950
|
import { render as render3 } from "ink";
|
|
14444
14951
|
|
|
14445
14952
|
// src/ui/tui.tsx
|
|
14446
14953
|
import fs10 from "fs/promises";
|
|
14447
|
-
import
|
|
14954
|
+
import path21 from "path";
|
|
14448
14955
|
import { Box as Box5, Text as Text5, useApp as useApp4, useInput as useInput4, useStdout, useIsScreenReaderEnabled as useIsScreenReaderEnabled5 } from "ink";
|
|
14449
14956
|
import { useEffect as useEffect5, useMemo, useState as useState5 } from "react";
|
|
14450
14957
|
import { Fragment, jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
14451
14958
|
var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
14452
14959
|
function useTerminalColumns() {
|
|
14453
14960
|
const { stdout } = useStdout();
|
|
14961
|
+
const accessible = useIsScreenReaderEnabled5();
|
|
14454
14962
|
const [columns, setColumns] = useState5(stdout.columns ?? 80);
|
|
14455
14963
|
useEffect5(() => {
|
|
14456
|
-
const onResize = () =>
|
|
14457
|
-
|
|
14964
|
+
const onResize = () => {
|
|
14965
|
+
if (!accessible) {
|
|
14966
|
+
stdout.write("\x1B[2J\x1B[H");
|
|
14967
|
+
}
|
|
14968
|
+
setColumns(stdout.columns ?? 80);
|
|
14969
|
+
};
|
|
14970
|
+
stdout.prependListener("resize", onResize);
|
|
14458
14971
|
return () => {
|
|
14459
14972
|
stdout.off("resize", onResize);
|
|
14460
14973
|
};
|
|
14461
|
-
}, [stdout]);
|
|
14974
|
+
}, [stdout, accessible]);
|
|
14462
14975
|
return columns;
|
|
14463
14976
|
}
|
|
14464
14977
|
function useSpinner(active) {
|
|
@@ -14543,6 +15056,7 @@ function AgentRCTui({ repoPath, skipAnimation = false }) {
|
|
|
14543
15056
|
const [modelPickTarget, setModelPickTarget] = useState5("eval");
|
|
14544
15057
|
const [modelCursor, setModelCursor] = useState5(0);
|
|
14545
15058
|
const [hasEvalConfig, setHasEvalConfig] = useState5(null);
|
|
15059
|
+
const [hasAgentrcConfig, setHasAgentrcConfig] = useState5(null);
|
|
14546
15060
|
const [activityLog, setActivityLog] = useState5([]);
|
|
14547
15061
|
const [generateTarget, setGenerateTarget] = useState5(
|
|
14548
15062
|
"copilot-instructions"
|
|
@@ -14552,9 +15066,9 @@ function AgentRCTui({ repoPath, skipAnimation = false }) {
|
|
|
14552
15066
|
const [isMonorepo, setIsMonorepo] = useState5(false);
|
|
14553
15067
|
const [repoAreas, setRepoAreas] = useState5([]);
|
|
14554
15068
|
const [areaCursor, setAreaCursor] = useState5(0);
|
|
14555
|
-
const repoLabel = useMemo(() =>
|
|
15069
|
+
const repoLabel = useMemo(() => path21.basename(repoPath), [repoPath]);
|
|
14556
15070
|
const repoFull = useMemo(() => repoPath, [repoPath]);
|
|
14557
|
-
const isLoading = status === "generating" || status === "bootstrapping" || status === "evaluating" || status === "generating-areas";
|
|
15071
|
+
const isLoading = status === "generating" || status === "bootstrapping" || status === "evaluating" || status === "generating-areas" || status === "readiness-running";
|
|
14558
15072
|
const isMenu = status === "model-pick" || status === "eval-pick" || status === "batch-pick" || status === "generate-pick" || status === "generate-app-pick" || status === "generate-area-pick";
|
|
14559
15073
|
const spinner = useSpinner(isLoading);
|
|
14560
15074
|
const addLog = (text, type = "info") => {
|
|
@@ -14564,14 +15078,24 @@ function AgentRCTui({ repoPath, skipAnimation = false }) {
|
|
|
14564
15078
|
setStatus("idle");
|
|
14565
15079
|
};
|
|
14566
15080
|
useEffect5(() => {
|
|
14567
|
-
const configPath =
|
|
15081
|
+
const configPath = path21.join(repoPath, "agentrc.eval.json");
|
|
14568
15082
|
fs10.access(configPath).then(() => setHasEvalConfig(true)).catch(() => setHasEvalConfig(false));
|
|
15083
|
+
const agentrcConfigCandidates = [
|
|
15084
|
+
path21.join(repoPath, "agentrc.config.json"),
|
|
15085
|
+
path21.join(repoPath, ".github", "agentrc.config.json")
|
|
15086
|
+
];
|
|
15087
|
+
Promise.all(
|
|
15088
|
+
agentrcConfigCandidates.map(
|
|
15089
|
+
(p) => fs10.access(p).then(() => true).catch(() => false)
|
|
15090
|
+
)
|
|
15091
|
+
).then((results) => setHasAgentrcConfig(results.some(Boolean))).catch(() => setHasAgentrcConfig(false));
|
|
14569
15092
|
analyzeRepo(repoPath).then((analysis) => {
|
|
14570
15093
|
const apps = analysis.apps ?? [];
|
|
14571
15094
|
setRepoApps(apps);
|
|
14572
15095
|
setIsMonorepo(analysis.isMonorepo ?? false);
|
|
14573
15096
|
setRepoAreas(analysis.areas ?? []);
|
|
14574
15097
|
}).catch((err) => {
|
|
15098
|
+
setRepoAreas([]);
|
|
14575
15099
|
addLog(`Repo analysis failed: ${err instanceof Error ? err.message : "unknown"}`, "error");
|
|
14576
15100
|
});
|
|
14577
15101
|
}, [repoPath]);
|
|
@@ -14579,17 +15103,17 @@ function AgentRCTui({ repoPath, skipAnimation = false }) {
|
|
|
14579
15103
|
let active = true;
|
|
14580
15104
|
listCopilotModels().then((models) => {
|
|
14581
15105
|
if (!active) return;
|
|
14582
|
-
|
|
14583
|
-
|
|
15106
|
+
const list = models.length > 0 ? models : PREFERRED_MODELS;
|
|
15107
|
+
setAvailableModels(list);
|
|
14584
15108
|
setEvalModel(
|
|
14585
|
-
(current) =>
|
|
15109
|
+
(current) => list.includes(current) ? current : pickBestModel(list, current)
|
|
14586
15110
|
);
|
|
14587
15111
|
setJudgeModel(
|
|
14588
|
-
(current) =>
|
|
15112
|
+
(current) => list.includes(current) ? current : pickBestModel(list, current)
|
|
14589
15113
|
);
|
|
14590
15114
|
}).catch(() => {
|
|
14591
15115
|
if (!active) return;
|
|
14592
|
-
setAvailableModels(
|
|
15116
|
+
setAvailableModels(PREFERRED_MODELS);
|
|
14593
15117
|
});
|
|
14594
15118
|
return () => {
|
|
14595
15119
|
active = false;
|
|
@@ -14625,7 +15149,7 @@ function AgentRCTui({ repoPath, skipAnimation = false }) {
|
|
|
14625
15149
|
};
|
|
14626
15150
|
useEffect5(() => {
|
|
14627
15151
|
let active = true;
|
|
14628
|
-
const configPath =
|
|
15152
|
+
const configPath = path21.join(repoPath, "agentrc.eval.json");
|
|
14629
15153
|
fs10.readFile(configPath, "utf8").then((raw) => {
|
|
14630
15154
|
if (!active) return;
|
|
14631
15155
|
const parsed = JSON.parse(raw);
|
|
@@ -14640,7 +15164,7 @@ function AgentRCTui({ repoPath, skipAnimation = false }) {
|
|
|
14640
15164
|
};
|
|
14641
15165
|
}, [repoPath]);
|
|
14642
15166
|
const bootstrapEvalConfig = async (count, force) => {
|
|
14643
|
-
const configPath =
|
|
15167
|
+
const configPath = path21.join(repoPath, "agentrc.eval.json");
|
|
14644
15168
|
try {
|
|
14645
15169
|
setStatus("bootstrapping");
|
|
14646
15170
|
setMessage("Generating eval cases with Copilot SDK...");
|
|
@@ -14679,13 +15203,13 @@ function AgentRCTui({ repoPath, skipAnimation = false }) {
|
|
|
14679
15203
|
if (status === "preview") {
|
|
14680
15204
|
if (input.toLowerCase() === "s") {
|
|
14681
15205
|
try {
|
|
14682
|
-
const outputPath = generateSavePath ||
|
|
14683
|
-
await fs10.mkdir(
|
|
15206
|
+
const outputPath = generateSavePath || path21.join(repoPath, ".github", "copilot-instructions.md");
|
|
15207
|
+
await fs10.mkdir(path21.dirname(outputPath), { recursive: true });
|
|
14684
15208
|
const { wrote, reason } = await safeWriteFile(outputPath, generatedContent, true);
|
|
14685
15209
|
if (!wrote)
|
|
14686
15210
|
throw new Error(reason === "symlink" ? "Path is a symlink" : "Write failed");
|
|
14687
15211
|
setStatus("done");
|
|
14688
|
-
const relPath =
|
|
15212
|
+
const relPath = path21.relative(repoPath, outputPath);
|
|
14689
15213
|
const msg = `Saved to ${relPath}`;
|
|
14690
15214
|
setMessage(msg);
|
|
14691
15215
|
addLog(msg, "success");
|
|
@@ -14719,7 +15243,7 @@ function AgentRCTui({ repoPath, skipAnimation = false }) {
|
|
|
14719
15243
|
setMessage("Enter a positive number of eval cases, then press Enter.");
|
|
14720
15244
|
return;
|
|
14721
15245
|
}
|
|
14722
|
-
const configPath =
|
|
15246
|
+
const configPath = path21.join(repoPath, "agentrc.eval.json");
|
|
14723
15247
|
setEvalBootstrapCount(count);
|
|
14724
15248
|
try {
|
|
14725
15249
|
await fs10.access(configPath);
|
|
@@ -14778,7 +15302,7 @@ function AgentRCTui({ repoPath, skipAnimation = false }) {
|
|
|
14778
15302
|
setStatus("generate-app-pick");
|
|
14779
15303
|
setMessage("Generate for root or per-app?");
|
|
14780
15304
|
} else {
|
|
14781
|
-
const savePath =
|
|
15305
|
+
const savePath = path21.join(repoPath, ".github", "copilot-instructions.md");
|
|
14782
15306
|
setGenerateSavePath(savePath);
|
|
14783
15307
|
await doGenerate(repoPath, savePath, "copilot-instructions");
|
|
14784
15308
|
}
|
|
@@ -14790,20 +15314,20 @@ function AgentRCTui({ repoPath, skipAnimation = false }) {
|
|
|
14790
15314
|
setStatus("generate-app-pick");
|
|
14791
15315
|
setMessage("Generate for root or per-app?");
|
|
14792
15316
|
} else {
|
|
14793
|
-
const savePath =
|
|
15317
|
+
const savePath = path21.join(repoPath, "AGENTS.md");
|
|
14794
15318
|
setGenerateSavePath(savePath);
|
|
14795
15319
|
await doGenerate(repoPath, savePath, "agents-md");
|
|
14796
15320
|
}
|
|
14797
15321
|
return;
|
|
14798
15322
|
}
|
|
14799
|
-
if (input.toLowerCase() === "
|
|
15323
|
+
if (input.toLowerCase() === "n") {
|
|
14800
15324
|
if (repoAreas.length === 0) {
|
|
14801
15325
|
setMessage("No areas detected. Add agentrc.config.json to define areas.");
|
|
14802
15326
|
return;
|
|
14803
15327
|
}
|
|
14804
15328
|
setAreaCursor(0);
|
|
14805
15329
|
setStatus("generate-area-pick");
|
|
14806
|
-
setMessage("Generate
|
|
15330
|
+
setMessage("Generate nested instructions for areas.");
|
|
14807
15331
|
return;
|
|
14808
15332
|
}
|
|
14809
15333
|
if (key.escape) {
|
|
@@ -14815,7 +15339,7 @@ function AgentRCTui({ repoPath, skipAnimation = false }) {
|
|
|
14815
15339
|
}
|
|
14816
15340
|
if (status === "generate-app-pick") {
|
|
14817
15341
|
if (input.toLowerCase() === "r") {
|
|
14818
|
-
const savePath = generateTarget === "copilot-instructions" ?
|
|
15342
|
+
const savePath = generateTarget === "copilot-instructions" ? path21.join(repoPath, ".github", "copilot-instructions.md") : path21.join(repoPath, "AGENTS.md");
|
|
14819
15343
|
setGenerateSavePath(savePath);
|
|
14820
15344
|
await doGenerate(repoPath, savePath, generateTarget);
|
|
14821
15345
|
return;
|
|
@@ -14825,7 +15349,7 @@ function AgentRCTui({ repoPath, skipAnimation = false }) {
|
|
|
14825
15349
|
addLog(`Generating ${generateTarget} for ${repoApps.length} apps...`, "progress");
|
|
14826
15350
|
let count = 0;
|
|
14827
15351
|
for (const app2 of repoApps) {
|
|
14828
|
-
const savePath = generateTarget === "copilot-instructions" ?
|
|
15352
|
+
const savePath = generateTarget === "copilot-instructions" ? path21.join(app2.path, ".github", "copilot-instructions.md") : path21.join(app2.path, "AGENTS.md");
|
|
14829
15353
|
setMessage(`Generating for ${app2.name} (${count + 1}/${repoApps.length})...`);
|
|
14830
15354
|
try {
|
|
14831
15355
|
const content = await generateCopilotInstructions({
|
|
@@ -14833,11 +15357,11 @@ function AgentRCTui({ repoPath, skipAnimation = false }) {
|
|
|
14833
15357
|
onProgress: (msg) => setMessage(`${app2.name}: ${msg}`)
|
|
14834
15358
|
});
|
|
14835
15359
|
if (content.trim()) {
|
|
14836
|
-
await fs10.mkdir(
|
|
15360
|
+
await fs10.mkdir(path21.dirname(savePath), { recursive: true });
|
|
14837
15361
|
const { wrote: saved } = await safeWriteFile(savePath, content, true);
|
|
14838
15362
|
if (!saved) continue;
|
|
14839
15363
|
count++;
|
|
14840
|
-
addLog(`${app2.name}: saved ${
|
|
15364
|
+
addLog(`${app2.name}: saved ${path21.basename(savePath)}`, "success");
|
|
14841
15365
|
}
|
|
14842
15366
|
} catch (error) {
|
|
14843
15367
|
const msg = error instanceof Error ? error.message : "Failed.";
|
|
@@ -14851,7 +15375,7 @@ function AgentRCTui({ repoPath, skipAnimation = false }) {
|
|
|
14851
15375
|
const num = Number.parseInt(input, 10);
|
|
14852
15376
|
if (Number.isFinite(num) && num >= 1 && num <= repoApps.length) {
|
|
14853
15377
|
const app2 = repoApps[num - 1];
|
|
14854
|
-
const savePath = generateTarget === "copilot-instructions" ?
|
|
15378
|
+
const savePath = generateTarget === "copilot-instructions" ? path21.join(app2.path, ".github", "copilot-instructions.md") : path21.join(app2.path, "AGENTS.md");
|
|
14855
15379
|
setGenerateSavePath(savePath);
|
|
14856
15380
|
await doGenerate(app2.path, savePath, generateTarget);
|
|
14857
15381
|
return;
|
|
@@ -14866,10 +15390,7 @@ function AgentRCTui({ repoPath, skipAnimation = false }) {
|
|
|
14866
15390
|
if (status === "generate-area-pick") {
|
|
14867
15391
|
if (input.toLowerCase() === "a") {
|
|
14868
15392
|
setStatus("generating-areas");
|
|
14869
|
-
addLog(
|
|
14870
|
-
`Generating file-based instructions for ${repoAreas.length} areas...`,
|
|
14871
|
-
"progress"
|
|
14872
|
-
);
|
|
15393
|
+
addLog(`Generating nested instructions for ${repoAreas.length} areas...`, "progress");
|
|
14873
15394
|
let written = 0;
|
|
14874
15395
|
for (const [i, area] of repoAreas.entries()) {
|
|
14875
15396
|
setMessage(`Generating for "${area.name}" (${i + 1}/${repoAreas.length})...`);
|
|
@@ -14882,7 +15403,7 @@ function AgentRCTui({ repoPath, skipAnimation = false }) {
|
|
|
14882
15403
|
const result = await writeAreaInstruction(repoPath, area, body);
|
|
14883
15404
|
if (result.status === "written") {
|
|
14884
15405
|
written++;
|
|
14885
|
-
addLog(`${area.name}: saved ${
|
|
15406
|
+
addLog(`${area.name}: saved ${path21.basename(result.filePath)}`, "success");
|
|
14886
15407
|
} else if (result.status === "skipped") {
|
|
14887
15408
|
addLog(`${area.name}: skipped (file exists)`, "info");
|
|
14888
15409
|
}
|
|
@@ -14892,9 +15413,7 @@ function AgentRCTui({ repoPath, skipAnimation = false }) {
|
|
|
14892
15413
|
}
|
|
14893
15414
|
}
|
|
14894
15415
|
setStatus("done");
|
|
14895
|
-
setMessage(
|
|
14896
|
-
`Generated file-based instructions for ${written}/${repoAreas.length} areas.`
|
|
14897
|
-
);
|
|
15416
|
+
setMessage(`Generated nested instructions for ${written}/${repoAreas.length} areas.`);
|
|
14898
15417
|
return;
|
|
14899
15418
|
}
|
|
14900
15419
|
if (key.upArrow) {
|
|
@@ -14910,7 +15429,7 @@ function AgentRCTui({ repoPath, skipAnimation = false }) {
|
|
|
14910
15429
|
if (!area) return;
|
|
14911
15430
|
setStatus("generating-areas");
|
|
14912
15431
|
setMessage(`Generating for "${area.name}"...`);
|
|
14913
|
-
addLog(`Generating
|
|
15432
|
+
addLog(`Generating nested instructions for "${area.name}"...`, "progress");
|
|
14914
15433
|
try {
|
|
14915
15434
|
const body = await generateAreaInstructions({
|
|
14916
15435
|
repoPath,
|
|
@@ -14978,8 +15497,8 @@ function AgentRCTui({ repoPath, skipAnimation = false }) {
|
|
|
14978
15497
|
}
|
|
14979
15498
|
if (status === "eval-pick") {
|
|
14980
15499
|
if (input.toLowerCase() === "r") {
|
|
14981
|
-
const configPath =
|
|
14982
|
-
const outputPath =
|
|
15500
|
+
const configPath = path21.join(repoPath, "agentrc.eval.json");
|
|
15501
|
+
const outputPath = path21.join(
|
|
14983
15502
|
repoPath,
|
|
14984
15503
|
".agentrc",
|
|
14985
15504
|
"evals",
|
|
@@ -15083,6 +15602,36 @@ function AgentRCTui({ repoPath, skipAnimation = false }) {
|
|
|
15083
15602
|
setMessage("Select what to generate.");
|
|
15084
15603
|
return;
|
|
15085
15604
|
}
|
|
15605
|
+
if (input.toLowerCase() === "r") {
|
|
15606
|
+
setStatus("readiness-running");
|
|
15607
|
+
setMessage("Running readiness report\u2026");
|
|
15608
|
+
addLog("Running readiness report\u2026", "progress");
|
|
15609
|
+
try {
|
|
15610
|
+
let policies;
|
|
15611
|
+
try {
|
|
15612
|
+
const config = await loadAgentrcConfig(repoPath);
|
|
15613
|
+
policies = config?.policies;
|
|
15614
|
+
} catch {
|
|
15615
|
+
}
|
|
15616
|
+
const report = await runReadinessReport({ repoPath, policies });
|
|
15617
|
+
const levelName2 = getLevelName(report.achievedLevel);
|
|
15618
|
+
const failCount = report.criteria.filter((c) => c.status === "fail").length;
|
|
15619
|
+
addLog(
|
|
15620
|
+
`Level ${report.achievedLevel} (${levelName2}) \u2014 ${failCount} item(s) to fix`,
|
|
15621
|
+
"success"
|
|
15622
|
+
);
|
|
15623
|
+
setStatus("done");
|
|
15624
|
+
setMessage(
|
|
15625
|
+
`Readiness: Level ${report.achievedLevel} (${levelName2}). ${failCount} improvement(s) available.`
|
|
15626
|
+
);
|
|
15627
|
+
} catch (error) {
|
|
15628
|
+
const msg = error instanceof Error ? error.message : "Failed.";
|
|
15629
|
+
addLog(`Readiness failed: ${msg}`, "error");
|
|
15630
|
+
setStatus("error");
|
|
15631
|
+
setMessage(`Readiness failed: ${msg}`);
|
|
15632
|
+
}
|
|
15633
|
+
return;
|
|
15634
|
+
}
|
|
15086
15635
|
if (input.toLowerCase() === "b") {
|
|
15087
15636
|
setStatus("batch-pick");
|
|
15088
15637
|
setMessage("Select batch provider.");
|
|
@@ -15093,6 +15642,36 @@ function AgentRCTui({ repoPath, skipAnimation = false }) {
|
|
|
15093
15642
|
setMessage("Select eval action.");
|
|
15094
15643
|
return;
|
|
15095
15644
|
}
|
|
15645
|
+
if (input.toLowerCase() === "c") {
|
|
15646
|
+
if (hasAgentrcConfig) {
|
|
15647
|
+
setMessage("agentrc.config.json already exists.");
|
|
15648
|
+
return;
|
|
15649
|
+
}
|
|
15650
|
+
setStatus("generating");
|
|
15651
|
+
setMessage("Creating agentrc.config.json...");
|
|
15652
|
+
addLog("Scaffolding agentrc.config.json...", "progress");
|
|
15653
|
+
try {
|
|
15654
|
+
const result = await scaffoldAgentrcConfig(repoPath, repoAreas);
|
|
15655
|
+
if (result.wrote) {
|
|
15656
|
+
setHasAgentrcConfig(true);
|
|
15657
|
+
setStatus("done");
|
|
15658
|
+
const msg = `Created agentrc.config.json`;
|
|
15659
|
+
setMessage(msg);
|
|
15660
|
+
addLog(msg, "success");
|
|
15661
|
+
} else {
|
|
15662
|
+
setHasAgentrcConfig(true);
|
|
15663
|
+
setStatus("idle");
|
|
15664
|
+
setMessage("agentrc.config.json already exists (skipped).");
|
|
15665
|
+
addLog("agentrc.config.json already exists.", "info");
|
|
15666
|
+
}
|
|
15667
|
+
} catch (error) {
|
|
15668
|
+
setStatus("error");
|
|
15669
|
+
const msg = error instanceof Error ? error.message : "Failed to create config.";
|
|
15670
|
+
setMessage(msg);
|
|
15671
|
+
addLog(msg, "error");
|
|
15672
|
+
}
|
|
15673
|
+
return;
|
|
15674
|
+
}
|
|
15096
15675
|
if (input.toLowerCase() === "m") {
|
|
15097
15676
|
if (hideModelPicker) {
|
|
15098
15677
|
setMessage(
|
|
@@ -15135,7 +15714,7 @@ function AgentRCTui({ repoPath, skipAnimation = false }) {
|
|
|
15135
15714
|
{ isActive: inputActive }
|
|
15136
15715
|
);
|
|
15137
15716
|
const statusIcon = status === "error" ? accessible ? "ERROR" : "\u2717" : status === "done" ? accessible ? "OK" : "\u2713" : isLoading ? spinner : accessible ? "*" : "\u25CF";
|
|
15138
|
-
const statusLabel = status === "intro" ? "starting" : status === "idle" ? "ready" : status === "bootstrapEvalCount" ? "input" : status === "bootstrapEvalConfirm" ? "confirm" : status === "eval-pick" ? "eval" : status === "batch-pick" ? "batch" : status === "model-pick" ? "models" : status;
|
|
15717
|
+
const statusLabel = status === "intro" ? "starting" : status === "idle" ? "ready" : status === "bootstrapEvalCount" ? "input" : status === "bootstrapEvalConfirm" ? "confirm" : status === "eval-pick" ? "eval" : status === "batch-pick" ? "batch" : status === "model-pick" ? "models" : status === "readiness-running" ? "readiness" : status;
|
|
15139
15718
|
const statusColor = status === "error" ? "red" : status === "done" ? "green" : isLoading ? "yellow" : isMenu ? "magentaBright" : "cyanBright";
|
|
15140
15719
|
const formatTokens = (result) => {
|
|
15141
15720
|
const withUsage = result.metrics?.withInstructions?.tokenUsage;
|
|
@@ -15207,6 +15786,10 @@ function AgentRCTui({ repoPath, skipAnimation = false }) {
|
|
|
15207
15786
|
/* @__PURE__ */ jsxs4(Text5, { children: [
|
|
15208
15787
|
/* @__PURE__ */ jsx7(Text5, { color: "gray", children: "Eval " }),
|
|
15209
15788
|
hasEvalConfig === null ? /* @__PURE__ */ jsx7(Text5, { color: "gray", dimColor: true, children: "checking..." }) : hasEvalConfig ? /* @__PURE__ */ jsx7(Text5, { color: "green", children: "agentrc.eval.json found" }) : /* @__PURE__ */ jsx7(Text5, { color: "yellow", children: "no eval config \u2014 press [I] to create" })
|
|
15789
|
+
] }),
|
|
15790
|
+
/* @__PURE__ */ jsxs4(Text5, { children: [
|
|
15791
|
+
/* @__PURE__ */ jsx7(Text5, { color: "gray", children: "Config " }),
|
|
15792
|
+
hasAgentrcConfig === null ? /* @__PURE__ */ jsx7(Text5, { color: "gray", dimColor: true, children: "checking..." }) : hasAgentrcConfig ? /* @__PURE__ */ jsx7(Text5, { color: "green", children: "agentrc.config.json found" }) : /* @__PURE__ */ jsx7(Text5, { color: "yellow", children: "no config \u2014 press [C] to create" })
|
|
15210
15793
|
] })
|
|
15211
15794
|
] }),
|
|
15212
15795
|
/* @__PURE__ */ jsx7(Divider, { columns: terminalColumns, label: "Activity", accessible }),
|
|
@@ -15282,19 +15865,12 @@ function AgentRCTui({ repoPath, skipAnimation = false }) {
|
|
|
15282
15865
|
/* @__PURE__ */ jsx7(Text5, { color: "white", children: app2.name }),
|
|
15283
15866
|
/* @__PURE__ */ jsxs4(Text5, { color: "gray", dimColor: true, children: [
|
|
15284
15867
|
" ",
|
|
15285
|
-
|
|
15868
|
+
path21.relative(repoPath, app2.path)
|
|
15286
15869
|
] })
|
|
15287
15870
|
] }, app2.name)) })
|
|
15288
15871
|
] }),
|
|
15289
15872
|
status === "generate-area-pick" && repoAreas.length > 0 && /* @__PURE__ */ jsxs4(Fragment, { children: [
|
|
15290
|
-
/* @__PURE__ */ jsx7(
|
|
15291
|
-
Divider,
|
|
15292
|
-
{
|
|
15293
|
-
columns: terminalColumns,
|
|
15294
|
-
label: "File-based instructions",
|
|
15295
|
-
accessible
|
|
15296
|
-
}
|
|
15297
|
-
),
|
|
15873
|
+
/* @__PURE__ */ jsx7(Divider, { columns: terminalColumns, label: "Nested instructions", accessible }),
|
|
15298
15874
|
/* @__PURE__ */ jsx7(Box5, { flexDirection: "column", paddingLeft: 1, children: repoAreas.map((area, i) => /* @__PURE__ */ jsxs4(Text5, { children: [
|
|
15299
15875
|
/* @__PURE__ */ jsx7(Text5, { color: i === areaCursor ? "cyanBright" : "gray", children: i === areaCursor ? accessible ? ">" : "\u25B6" : " " }),
|
|
15300
15876
|
/* @__PURE__ */ jsx7(Text5, { color: "gray", children: " " }),
|
|
@@ -15323,7 +15899,7 @@ function AgentRCTui({ repoPath, skipAnimation = false }) {
|
|
|
15323
15899
|
children: [
|
|
15324
15900
|
/* @__PURE__ */ jsxs4(Text5, { color: "cyan", bold: true, children: [
|
|
15325
15901
|
"Preview (",
|
|
15326
|
-
|
|
15902
|
+
path21.relative(repoPath, generateSavePath) || generateTarget,
|
|
15327
15903
|
")"
|
|
15328
15904
|
] }),
|
|
15329
15905
|
/* @__PURE__ */ jsx7(Text5, { color: "gray", children: truncatedPreview })
|
|
@@ -15375,9 +15951,9 @@ function AgentRCTui({ repoPath, skipAnimation = false }) {
|
|
|
15375
15951
|
/* @__PURE__ */ jsx7(Text5, { color: "cyan", children: " to select " }),
|
|
15376
15952
|
/* @__PURE__ */ jsx7(KeyHint, { k: "Esc", label: "Back" })
|
|
15377
15953
|
] }) : status === "generate-pick" ? /* @__PURE__ */ jsxs4(Box5, { children: [
|
|
15378
|
-
/* @__PURE__ */ jsx7(KeyHint, { k: "C", label: "
|
|
15379
|
-
/* @__PURE__ */ jsx7(KeyHint, { k: "A", label: "
|
|
15380
|
-
repoAreas.length > 0 && /* @__PURE__ */ jsx7(KeyHint, { k: "
|
|
15954
|
+
/* @__PURE__ */ jsx7(KeyHint, { k: "C", label: "Instructions" }),
|
|
15955
|
+
/* @__PURE__ */ jsx7(KeyHint, { k: "A", label: "Agents" }),
|
|
15956
|
+
repoAreas.length > 0 && /* @__PURE__ */ jsx7(KeyHint, { k: "N", label: "Nested (areas)" }),
|
|
15381
15957
|
/* @__PURE__ */ jsx7(KeyHint, { k: "Esc", label: "Back" })
|
|
15382
15958
|
] }) : status === "generate-app-pick" ? /* @__PURE__ */ jsxs4(Box5, { children: [
|
|
15383
15959
|
/* @__PURE__ */ jsx7(KeyHint, { k: "R", label: "Root only" }),
|
|
@@ -15413,12 +15989,14 @@ function AgentRCTui({ repoPath, skipAnimation = false }) {
|
|
|
15413
15989
|
] }) : isLoading ? /* @__PURE__ */ jsx7(Box5, { children: /* @__PURE__ */ jsx7(KeyHint, { k: "Q", label: "Quit" }) }) : /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", children: [
|
|
15414
15990
|
/* @__PURE__ */ jsxs4(Box5, { children: [
|
|
15415
15991
|
/* @__PURE__ */ jsx7(KeyHint, { k: "G", label: "Generate" }),
|
|
15992
|
+
/* @__PURE__ */ jsx7(KeyHint, { k: "R", label: "Readiness" }),
|
|
15416
15993
|
/* @__PURE__ */ jsx7(KeyHint, { k: "E", label: "Eval" }),
|
|
15417
15994
|
/* @__PURE__ */ jsx7(KeyHint, { k: "B", label: "Batch" })
|
|
15418
15995
|
] }),
|
|
15419
15996
|
/* @__PURE__ */ jsxs4(Box5, { children: [
|
|
15420
15997
|
/* @__PURE__ */ jsx7(KeyHint, { k: "M", label: "Model" }),
|
|
15421
15998
|
/* @__PURE__ */ jsx7(KeyHint, { k: "J", label: "Judge" }),
|
|
15999
|
+
hasAgentrcConfig === false && /* @__PURE__ */ jsx7(KeyHint, { k: "C", label: "Create config" }),
|
|
15422
16000
|
/* @__PURE__ */ jsx7(KeyHint, { k: "Q", label: "Quit" })
|
|
15423
16001
|
] })
|
|
15424
16002
|
] }) })
|
|
@@ -15430,7 +16008,7 @@ function AgentRCTui({ repoPath, skipAnimation = false }) {
|
|
|
15430
16008
|
// src/commands/tui.tsx
|
|
15431
16009
|
import { jsx as jsx8 } from "react/jsx-runtime";
|
|
15432
16010
|
async function tuiCommand(options) {
|
|
15433
|
-
const repoPath =
|
|
16011
|
+
const repoPath = path22.resolve(options.repo ?? process.cwd());
|
|
15434
16012
|
const skipAnimation = options.animation === false;
|
|
15435
16013
|
try {
|
|
15436
16014
|
const accessible = options.accessible ? true : void 0;
|
|
@@ -15445,7 +16023,7 @@ async function tuiCommand(options) {
|
|
|
15445
16023
|
}
|
|
15446
16024
|
|
|
15447
16025
|
// src/cli.ts
|
|
15448
|
-
var _require =
|
|
16026
|
+
var _require = createRequire2(import.meta.url);
|
|
15449
16027
|
var CLI_VERSION = _require("../package.json").version;
|
|
15450
16028
|
function withGlobalOpts(fn) {
|
|
15451
16029
|
return async (...raw) => {
|
|
@@ -15466,7 +16044,7 @@ function withGlobalOpts(fn) {
|
|
|
15466
16044
|
function runCli(argv) {
|
|
15467
16045
|
const program = new Command();
|
|
15468
16046
|
program.name("agentrc").description("Set up repositories for AI-assisted development").version(CLI_VERSION).option("--json", "Output machine-readable JSON to stdout").option("--quiet", "Suppress stderr progress output").option("--accessible", "Enable screen reader friendly output");
|
|
15469
|
-
program.command("init").description("
|
|
16047
|
+
program.command("init").description("Init repository \u2014 analyze & generate instructions").argument("[path]", "Path to a local repository").option("--github", "Use a GitHub repository").option("--provider <provider>", "Repo provider (github|azure)").option("--yes", "Accept defaults (generates instructions, MCP, and VS Code configs)").option("--force", "Overwrite existing files").option("--model <name>", "Model for instructions generation", DEFAULT_MODEL).action(withGlobalOpts(initCommand));
|
|
15470
16048
|
program.command("analyze").description("Detect languages, frameworks, monorepo structure, and areas").argument("[path]", "Path to a local repository").option("--output <path>", "Write report to file (.json or .md)").option("--force", "Overwrite existing output file").action(withGlobalOpts(analyzeCommand));
|
|
15471
16049
|
program.command("generate").description("Generate instructions, agents, MCP, or VS Code configs").addArgument(
|
|
15472
16050
|
new Argument("<type>", "Config type to generate").choices([
|
|
@@ -15475,14 +16053,14 @@ function runCli(argv) {
|
|
|
15475
16053
|
"mcp",
|
|
15476
16054
|
"vscode"
|
|
15477
16055
|
])
|
|
15478
|
-
).argument("[path]", "Path to a local repository").option("--force", "Overwrite existing files").option("--per-app", "
|
|
16056
|
+
).argument("[path]", "Path to a local repository").option("--force", "Overwrite existing files").option("--per-app", "(deprecated) Use `agentrc instructions --areas` instead").option("--model <name>", "Model for instructions generation", DEFAULT_MODEL).option("--strategy <mode>", "Instruction strategy (flat or nested)").option("--dry-run", "Preview generated files without writing anything").action(withGlobalOpts(generateCommand));
|
|
15479
16057
|
program.command("pr").description("Create a PR with generated configs on GitHub or Azure DevOps").argument("[repo]", "Repo identifier (github: owner/name, azure: org/project/repo)").option("--branch <name>", "Branch name").option("--provider <provider>", "Repo provider (github|azure)").option("--model <name>", "Model for instructions generation", DEFAULT_MODEL).action(withGlobalOpts(prCommand));
|
|
15480
16058
|
program.command("eval").description("Compare AI responses with and without instructions").argument("[path]", "Path to eval config JSON").option("--repo <path>", "Repository path", process.cwd()).option("--model <name>", "Model for responses", DEFAULT_MODEL).option("--judge-model <name>", "Model for judging", DEFAULT_JUDGE_MODEL).option("--list-models", "List Copilot CLI models and exit").option("--output <path>", "Write results JSON to file").option("--init", "Create a starter agentrc.eval.json file").option("--count <number>", "Number of eval cases to generate (with --init)").option("--fail-level <number>", "Exit with error if pass rate (%) falls below threshold").action(withGlobalOpts(evalCommand));
|
|
15481
16059
|
program.command("tui").description("Interactive terminal UI for generation, evaluation, and batch workflows").option("--repo <path>", "Repository path", process.cwd()).option("--no-animation", "Skip the animated banner intro").action(withGlobalOpts(tuiCommand));
|
|
15482
|
-
program.command("instructions").description("Generate
|
|
15483
|
-
program.command("readiness").description("
|
|
16060
|
+
program.command("instructions").description("Generate instructions for the repository").option("--repo <path>", "Repository path", process.cwd()).option("--output <path>", "Output path for instructions").option("--model <name>", "Model for instructions generation", DEFAULT_MODEL).option("--force", "Overwrite existing area instruction files").option("--areas", "Also generate instructions for detected areas").option("--areas-only", "Generate only area instructions (skip root)").option("--area <name>", "Generate instructions for a specific area").option("--strategy <mode>", "Instruction strategy (flat or nested)").option("--claude-md", "Generate CLAUDE.md files alongside AGENTS.md (nested strategy)").option("--dry-run", "Preview generated files without writing anything").action(withGlobalOpts(instructionsCommand));
|
|
16061
|
+
program.command("readiness").description("Run readiness report across 9 maturity pillars").argument("[path]", "Path to a local repository").option("--output <path>", "Write report to file (.json, .md, or .html)").option("--force", "Overwrite existing output file").option("--visual", "Generate visual HTML report").option("--per-area", "Show per-area readiness breakdown").option("--policy <sources>", "Policy sources (comma-separated: paths, npm packages)").option("--fail-level <number>", "Exit with error if readiness level is below threshold (1\u20135)").action(withGlobalOpts(readinessCommand));
|
|
15484
16062
|
program.command("batch").description("Batch process multiple repos across orgs").argument("[repos...]", "Repos in owner/name form (GitHub) or org/project/repo (Azure)").option("--output <path>", "Write results JSON to file").option("--provider <provider>", "Repo provider (github|azure)", "github").option("--model <name>", "Model for instructions generation", DEFAULT_MODEL).option("--branch <name>", "Branch name for PRs").action(withGlobalOpts(batchCommand));
|
|
15485
|
-
program.command("batch-readiness").description("
|
|
16063
|
+
program.command("batch-readiness").description("Run batch readiness report for multiple repos").option("--output <path>", "Write HTML report to file").option("--policy <sources>", "Policy sources (comma-separated: paths, npm packages)").action(withGlobalOpts(batchReadinessCommand));
|
|
15486
16064
|
program.parse(argv);
|
|
15487
16065
|
}
|
|
15488
16066
|
|