@ryuenn3123/agentic-senior-core 4.1.0 → 4.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/.agent-context/prompts/compact-natural-mode.md +100 -0
  2. package/.agent-context/prompts/init-project.md +1 -0
  3. package/.agent-context/prompts/refactor.md +1 -0
  4. package/.agent-context/review-checklists/pr-checklist.md +1 -0
  5. package/.agent-context/rules/architecture.md +10 -0
  6. package/.agent-context/rules/naming-conv.md +6 -3
  7. package/.agent-context/state/README.md +2 -1
  8. package/AGENTS.md +6 -8
  9. package/README.md +95 -117
  10. package/benchmarks/README.md +60 -0
  11. package/benchmarks/compact-natural-mode/fixtures.mjs +359 -0
  12. package/benchmarks/compact-natural-mode/scorer.mjs +331 -0
  13. package/benchmarks/runtime-token-saver/fixtures.mjs +714 -0
  14. package/bin/agentic-senior-core.js +6 -0
  15. package/bin/ascx.js +23 -0
  16. package/lib/cli/adaptive-context/catalog.mjs +428 -0
  17. package/lib/cli/adaptive-context/file-signals.mjs +100 -0
  18. package/lib/cli/adaptive-context/implications.mjs +44 -0
  19. package/lib/cli/adaptive-context.mjs +365 -0
  20. package/lib/cli/ascx/adapters/git-diff.mjs +223 -0
  21. package/lib/cli/ascx/adapters/git-status.mjs +145 -0
  22. package/lib/cli/ascx/adapters/npm-run-build.mjs +99 -0
  23. package/lib/cli/ascx/adapters/npm-test.mjs +120 -0
  24. package/lib/cli/ascx/adapters/rg.mjs +39 -0
  25. package/lib/cli/ascx/fixture-evaluator.mjs +180 -0
  26. package/lib/cli/ascx/formatter.mjs +47 -0
  27. package/lib/cli/ascx/lexer.mjs +129 -0
  28. package/lib/cli/ascx/runtime.mjs +192 -0
  29. package/lib/cli/ascx/tee-writer.mjs +63 -0
  30. package/lib/cli/ascx/token-estimate.mjs +15 -0
  31. package/lib/cli/backup.mjs +37 -4
  32. package/lib/cli/commands/context.mjs +140 -0
  33. package/lib/cli/commands/init.mjs +14 -2
  34. package/lib/cli/commands/optimize.mjs +143 -2
  35. package/lib/cli/commands/upgrade/design-intent-seed.mjs +46 -0
  36. package/lib/cli/commands/upgrade/token-optimization-state.mjs +51 -0
  37. package/lib/cli/commands/upgrade.mjs +34 -45
  38. package/lib/cli/compiler.mjs +9 -0
  39. package/lib/cli/project-scaffolder/prompt-builders.mjs +1 -0
  40. package/lib/cli/token-optimization.mjs +161 -6
  41. package/lib/cli/utils.mjs +15 -1
  42. package/package.json +10 -3
  43. package/scripts/adaptive-context/fixtures.mjs +188 -0
  44. package/scripts/adaptive-context-benchmark.mjs +9 -0
  45. package/scripts/ascx-runtime-token-saver-benchmark.mjs +9 -0
  46. package/scripts/build-release-benchmark-bundle.mjs +1 -3
  47. package/scripts/clean-local-artifacts.mjs +2 -0
  48. package/scripts/compact-natural-mode-benchmark.mjs +9 -0
  49. package/scripts/validate/config.mjs +6 -0
  50. package/scripts/validate.mjs +2 -0
@@ -0,0 +1,129 @@
1
+ const SHELL_OPERATOR_TOKENS = new Set([
2
+ '|',
3
+ '||',
4
+ '&',
5
+ '&&',
6
+ ';',
7
+ '>',
8
+ '>>',
9
+ '<',
10
+ '2>',
11
+ '2>>',
12
+ ]);
13
+
14
+ function isEnvironmentAssignment(argumentValue) {
15
+ return /^[A-Za-z_][A-Za-z0-9_]*=.+$/u.test(argumentValue);
16
+ }
17
+
18
+ function hasCommandSubstitution(argumentValue) {
19
+ return argumentValue.includes('$(') || argumentValue.includes('`');
20
+ }
21
+
22
+ function hasRedirectToken(argumentValue) {
23
+ return /^(?:[12]?>|[12]?>>|<)/u.test(argumentValue);
24
+ }
25
+
26
+ export function parseAscxCommand(commandArguments = []) {
27
+ const rawArguments = commandArguments.map((argumentValue) => String(argumentValue));
28
+ const unsafeTokens = rawArguments.filter((argumentValue) => {
29
+ return SHELL_OPERATOR_TOKENS.has(argumentValue)
30
+ || hasCommandSubstitution(argumentValue)
31
+ || hasRedirectToken(argumentValue);
32
+ });
33
+ const environment = [];
34
+ let executableIndex = 0;
35
+
36
+ while (
37
+ executableIndex < rawArguments.length
38
+ && isEnvironmentAssignment(rawArguments[executableIndex])
39
+ ) {
40
+ environment.push(rawArguments[executableIndex]);
41
+ executableIndex += 1;
42
+ }
43
+
44
+ const executable = rawArguments[executableIndex] || '';
45
+ const args = executable ? rawArguments.slice(executableIndex + 1) : [];
46
+
47
+ return {
48
+ rawArguments,
49
+ commandText: rawArguments.join(' '),
50
+ environment,
51
+ executable,
52
+ args,
53
+ unsafeTokens,
54
+ hasShellSyntax: unsafeTokens.length > 0,
55
+ hasEnvironmentPrefix: environment.length > 0,
56
+ };
57
+ }
58
+
59
+ export function classifyAscxInvocation(parsedCommand) {
60
+ if (!parsedCommand.executable) {
61
+ return {
62
+ kind: 'passthrough',
63
+ adapterName: null,
64
+ reason: 'missing executable',
65
+ };
66
+ }
67
+
68
+ if (parsedCommand.hasShellSyntax) {
69
+ return {
70
+ kind: 'unsafe-for-compression',
71
+ adapterName: null,
72
+ reason: `shell syntax detected: ${parsedCommand.unsafeTokens.join(', ')}`,
73
+ };
74
+ }
75
+
76
+ if (parsedCommand.hasEnvironmentPrefix) {
77
+ return {
78
+ kind: 'passthrough',
79
+ adapterName: null,
80
+ reason: 'environment prefix detected',
81
+ };
82
+ }
83
+
84
+ if (parsedCommand.executable === 'git' && parsedCommand.args[0] === 'status') {
85
+ return {
86
+ kind: 'compressible',
87
+ adapterName: 'git-status',
88
+ reason: 'supported git status adapter',
89
+ };
90
+ }
91
+
92
+ if (parsedCommand.executable === 'git' && parsedCommand.args[0] === 'diff') {
93
+ return {
94
+ kind: 'compressible',
95
+ adapterName: 'git-diff',
96
+ reason: 'supported git diff adapter',
97
+ };
98
+ }
99
+
100
+ if (parsedCommand.executable === 'npm' && parsedCommand.args[0] === 'test') {
101
+ return {
102
+ kind: 'compressible',
103
+ adapterName: 'npm-test',
104
+ reason: 'supported npm test adapter',
105
+ };
106
+ }
107
+
108
+ if (parsedCommand.executable === 'npm' && parsedCommand.args[0] === 'run' && parsedCommand.args[1] === 'build') {
109
+ return {
110
+ kind: 'compressible',
111
+ adapterName: 'npm-run-build',
112
+ reason: 'supported npm run build adapter',
113
+ };
114
+ }
115
+
116
+ if (parsedCommand.executable === 'rg') {
117
+ return {
118
+ kind: 'compressible',
119
+ adapterName: 'rg',
120
+ reason: 'supported rg (ripgrep) adapter',
121
+ };
122
+ }
123
+
124
+ return {
125
+ kind: 'passthrough',
126
+ adapterName: null,
127
+ reason: 'unsupported command',
128
+ };
129
+ }
@@ -0,0 +1,192 @@
1
+ import { spawn } from 'node:child_process';
2
+
3
+ import { compressGitDiffOutput } from './adapters/git-diff.mjs';
4
+ import { compressGitStatusOutput } from './adapters/git-status.mjs';
5
+ import { compressNpmTestOutput } from './adapters/npm-test.mjs';
6
+ import { compressNpmRunBuildOutput } from './adapters/npm-run-build.mjs';
7
+ import { compressRgOutput } from './adapters/rg.mjs';
8
+ import { buildAscxFooter, shouldWriteSafetyTee } from './formatter.mjs';
9
+ import { classifyAscxInvocation, parseAscxCommand } from './lexer.mjs';
10
+ import { writeRawTeeFile } from './tee-writer.mjs';
11
+
12
+ const ADAPTERS = {
13
+ 'git-diff': compressGitDiffOutput,
14
+ 'git-status': compressGitStatusOutput,
15
+ 'npm-test': compressNpmTestOutput,
16
+ 'npm-run-build': compressNpmRunBuildOutput,
17
+ 'rg': compressRgOutput,
18
+ };
19
+
20
+ function buildCommandEnvironment(baseEnvironment, parsedCommand) {
21
+ const commandEnvironment = { ...baseEnvironment };
22
+
23
+ for (const assignment of parsedCommand.environment) {
24
+ const separatorIndex = assignment.indexOf('=');
25
+ const variableName = assignment.slice(0, separatorIndex);
26
+ const variableValue = assignment.slice(separatorIndex + 1);
27
+
28
+ commandEnvironment[variableName] = variableValue;
29
+ }
30
+
31
+ return commandEnvironment;
32
+ }
33
+
34
+ function runSpawnedCommand(parsedCommand, options = {}) {
35
+ const {
36
+ cwd = process.cwd(),
37
+ env = process.env,
38
+ shell = false,
39
+ } = options;
40
+
41
+ return new Promise((resolve) => {
42
+ const useWindowsNpmShell = process.platform === 'win32'
43
+ && parsedCommand.executable === 'npm'
44
+ && shell === false;
45
+ const executable = shell
46
+ ? parsedCommand.commandText
47
+ : parsedCommand.executable;
48
+ const args = shell ? [] : parsedCommand.args;
49
+ const childProcess = spawn(executable, args, {
50
+ cwd,
51
+ env: buildCommandEnvironment(env, parsedCommand),
52
+ shell: shell || useWindowsNpmShell,
53
+ windowsHide: true,
54
+ });
55
+ let stdout = '';
56
+ let stderr = '';
57
+
58
+ childProcess.stdout?.setEncoding('utf8');
59
+ childProcess.stderr?.setEncoding('utf8');
60
+ childProcess.stdout?.on('data', (chunk) => {
61
+ stdout += chunk;
62
+ });
63
+ childProcess.stderr?.on('data', (chunk) => {
64
+ stderr += chunk;
65
+ });
66
+ childProcess.on('error', (error) => {
67
+ stderr += `${error.name}: ${error.message}\n`;
68
+ });
69
+ childProcess.on('close', (exitCode) => {
70
+ resolve({
71
+ stdout,
72
+ stderr,
73
+ exitCode: typeof exitCode === 'number' ? exitCode : 1,
74
+ });
75
+ });
76
+ });
77
+ }
78
+
79
+ function combineOutput(stdout, stderr) {
80
+ return [stdout, stderr].filter(Boolean).join('\n');
81
+ }
82
+
83
+ async function formatCompressedResult({
84
+ adapterResult,
85
+ capture,
86
+ classification,
87
+ commandText,
88
+ cwd,
89
+ teeDirectoryPath,
90
+ }) {
91
+ const rawOutput = combineOutput(capture.stdout, capture.stderr);
92
+ const preliminaryFooter = buildAscxFooter({
93
+ classification: classification.kind,
94
+ commandText,
95
+ compactOutput: adapterResult.output,
96
+ exitCode: capture.exitCode,
97
+ filterName: adapterResult.filterName,
98
+ rawOutput,
99
+ rawTeePath: null,
100
+ });
101
+ const rawTeePath = shouldWriteSafetyTee({
102
+ adapterResult,
103
+ exitCode: capture.exitCode,
104
+ reductionPercent: preliminaryFooter.reductionPercent,
105
+ })
106
+ ? await writeRawTeeFile({
107
+ commandText,
108
+ cwd,
109
+ exitCode: capture.exitCode,
110
+ rawOutput,
111
+ teeDirectoryPath,
112
+ })
113
+ : null;
114
+ const footer = buildAscxFooter({
115
+ classification: classification.kind,
116
+ commandText,
117
+ compactOutput: adapterResult.output,
118
+ exitCode: capture.exitCode,
119
+ filterName: adapterResult.filterName,
120
+ rawOutput,
121
+ rawTeePath,
122
+ });
123
+
124
+ return {
125
+ stdout: `${adapterResult.output}\n\n${footer.text}\n`,
126
+ stderr: '',
127
+ exitCode: capture.exitCode,
128
+ compressed: adapterResult.confident === true,
129
+ rawTeePath,
130
+ footer,
131
+ adapterResult,
132
+ };
133
+ }
134
+
135
+ export async function runAscx(commandArguments, options = {}) {
136
+ const {
137
+ cwd = process.cwd(),
138
+ executeCommand = runSpawnedCommand,
139
+ teeDirectoryPath,
140
+ } = options;
141
+ const parsedCommand = parseAscxCommand(commandArguments);
142
+ const classification = classifyAscxInvocation(parsedCommand);
143
+
144
+ if (!parsedCommand.executable) {
145
+ return {
146
+ stdout: '',
147
+ stderr: 'ascx: command is required\n',
148
+ exitCode: 1,
149
+ parsedCommand,
150
+ classification,
151
+ compressed: false,
152
+ rawTeePath: null,
153
+ };
154
+ }
155
+
156
+ const capture = await executeCommand(parsedCommand, {
157
+ cwd,
158
+ shell: classification.kind === 'unsafe-for-compression',
159
+ });
160
+
161
+ if (classification.kind !== 'compressible') {
162
+ return {
163
+ stdout: capture.stdout,
164
+ stderr: capture.stderr,
165
+ exitCode: capture.exitCode,
166
+ parsedCommand,
167
+ classification,
168
+ compressed: false,
169
+ rawTeePath: null,
170
+ };
171
+ }
172
+
173
+ const adapter = ADAPTERS[classification.adapterName];
174
+ const adapterResult = adapter({
175
+ stdout: capture.stdout,
176
+ stderr: capture.stderr,
177
+ exitCode: capture.exitCode,
178
+ });
179
+
180
+ return {
181
+ ...await formatCompressedResult({
182
+ adapterResult,
183
+ capture,
184
+ classification,
185
+ commandText: parsedCommand.commandText,
186
+ cwd,
187
+ teeDirectoryPath,
188
+ }),
189
+ parsedCommand,
190
+ classification,
191
+ };
192
+ }
@@ -0,0 +1,63 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+
4
+ export const MAX_TEE_FILES = 20;
5
+
6
+ function sanitizeFileNamePart(rawValue) {
7
+ return String(rawValue || 'command')
8
+ .toLowerCase()
9
+ .replace(/[^a-z0-9._-]+/g, '-')
10
+ .replace(/^-+|-+$/g, '')
11
+ .slice(0, 60) || 'command';
12
+ }
13
+
14
+ export function getDefaultTeeDirectory(cwd = process.cwd()) {
15
+ return path.resolve(cwd, '.agent-context', 'state', 'token-saver', 'tee');
16
+ }
17
+
18
+ async function sweepOldTeeFiles(directoryPath, maxFiles = MAX_TEE_FILES) {
19
+ let entries;
20
+
21
+ try {
22
+ entries = await fs.readdir(directoryPath);
23
+ } catch {
24
+ return;
25
+ }
26
+
27
+ const logFiles = entries.filter((name) => name.endsWith('.log')).sort();
28
+ const excess = logFiles.length - maxFiles;
29
+
30
+ for (let i = 0; i < excess; i++) {
31
+ try {
32
+ await fs.unlink(path.join(directoryPath, logFiles[i]));
33
+ } catch {
34
+ // Best-effort cleanup; do not fail the write path.
35
+ }
36
+ }
37
+ }
38
+
39
+ export async function writeRawTeeFile({
40
+ commandText,
41
+ cwd = process.cwd(),
42
+ exitCode,
43
+ rawOutput,
44
+ teeDirectoryPath = getDefaultTeeDirectory(cwd),
45
+ }) {
46
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
47
+ const commandName = sanitizeFileNamePart(commandText);
48
+ const teeFilePath = path.resolve(teeDirectoryPath, `${timestamp}-${commandName}.log`);
49
+ const fileContent = [
50
+ `[ascx raw output]`,
51
+ `command: ${commandText}`,
52
+ `exit: ${exitCode}`,
53
+ '',
54
+ rawOutput,
55
+ ].join('\n');
56
+
57
+ await fs.mkdir(path.dirname(teeFilePath), { recursive: true });
58
+ await fs.writeFile(teeFilePath, fileContent, 'utf8');
59
+ await sweepOldTeeFiles(teeDirectoryPath);
60
+
61
+ return teeFilePath;
62
+ }
63
+
@@ -0,0 +1,15 @@
1
+ export const ASCX_TOKEN_ESTIMATE_METHOD = 'chars-div-4-local-estimate';
2
+
3
+ export function estimateOutputTokens(outputText) {
4
+ const textLength = String(outputText || '').length;
5
+ return Math.max(0, Math.ceil(textLength / 4));
6
+ }
7
+
8
+ export function calculateReductionPercent(rawTokens, compactTokens) {
9
+ if (rawTokens <= 0) {
10
+ return 0;
11
+ }
12
+
13
+ const reducedTokens = Math.max(0, rawTokens - compactTokens);
14
+ return Number(((reducedTokens / rawTokens) * 100).toFixed(2));
15
+ }
@@ -6,6 +6,11 @@ import { entryPointFiles, BACKUP_DIR_NAME } from './constants.mjs';
6
6
 
7
7
  const BACKUP_GITIGNORE_ENTRY = `${BACKUP_DIR_NAME}/`;
8
8
  const BACKUP_GITIGNORE_COMMENT = '# agentic-senior-core: local backup artifacts';
9
+ const RUNTIME_ARTIFACT_GITIGNORE_ENTRIES = [
10
+ '.agent-context/state/token-saver/',
11
+ '.agent-context/state/token-optimization-report.json',
12
+ ];
13
+ const RUNTIME_ARTIFACT_GITIGNORE_COMMENT = '# agentic-senior-core: local runtime artifacts';
9
14
 
10
15
  /**
11
16
  * Calculates a SHA-256 hash of a file's contents.
@@ -127,18 +132,23 @@ export async function createBackup(targetDirectoryPath) {
127
132
  };
128
133
  }
129
134
 
130
- export async function ensureBackupGitignoreEntry(targetDirectoryPath) {
135
+ async function ensureGitignoreEntries(targetDirectoryPath, { comment, entries, aliasesByEntry = {} }) {
131
136
  const gitignorePath = path.join(targetDirectoryPath, '.gitignore');
132
137
  const existingContent = await pathExists(gitignorePath)
133
138
  ? await fs.readFile(gitignorePath, 'utf8')
134
139
  : '';
135
140
  const existingLines = existingContent.split(/\r?\n/).map((line) => line.trim());
141
+ const missingEntries = entries.filter((entry) => {
142
+ const equivalentEntries = [entry, ...(aliasesByEntry[entry] || [])];
143
+ return !equivalentEntries.some((equivalentEntry) => existingLines.includes(equivalentEntry));
144
+ });
136
145
 
137
- if (existingLines.includes(BACKUP_GITIGNORE_ENTRY) || existingLines.includes(BACKUP_DIR_NAME)) {
146
+ if (missingEntries.length === 0) {
138
147
  return {
139
148
  status: 'unchanged',
140
149
  gitignorePath,
141
- entry: BACKUP_GITIGNORE_ENTRY,
150
+ entries,
151
+ addedEntries: [],
142
152
  };
143
153
  }
144
154
 
@@ -149,13 +159,36 @@ export async function ensureBackupGitignoreEntry(targetDirectoryPath) {
149
159
  if (nextContent.length > 0 && !nextContent.endsWith('\n\n')) {
150
160
  nextContent += '\n';
151
161
  }
152
- nextContent += `${BACKUP_GITIGNORE_COMMENT}\n${BACKUP_GITIGNORE_ENTRY}\n`;
162
+ nextContent += `${comment}\n${missingEntries.join('\n')}\n`;
153
163
 
154
164
  await fs.writeFile(gitignorePath, nextContent, 'utf8');
155
165
 
156
166
  return {
157
167
  status: existingContent ? 'updated' : 'created',
158
168
  gitignorePath,
169
+ entries,
170
+ addedEntries: missingEntries,
171
+ };
172
+ }
173
+
174
+ export async function ensureBackupGitignoreEntry(targetDirectoryPath) {
175
+ const result = await ensureGitignoreEntries(targetDirectoryPath, {
176
+ comment: BACKUP_GITIGNORE_COMMENT,
177
+ entries: [BACKUP_GITIGNORE_ENTRY],
178
+ aliasesByEntry: {
179
+ [BACKUP_GITIGNORE_ENTRY]: [BACKUP_DIR_NAME],
180
+ },
181
+ });
182
+
183
+ return {
184
+ ...result,
159
185
  entry: BACKUP_GITIGNORE_ENTRY,
160
186
  };
161
187
  }
188
+
189
+ export async function ensureRuntimeArtifactGitignoreEntries(targetDirectoryPath) {
190
+ return ensureGitignoreEntries(targetDirectoryPath, {
191
+ comment: RUNTIME_ARTIFACT_GITIGNORE_COMMENT,
192
+ entries: RUNTIME_ARTIFACT_GITIGNORE_ENTRIES,
193
+ });
194
+ }
@@ -0,0 +1,140 @@
1
+ import { stdin } from 'node:process';
2
+
3
+ import { buildSelectedContextManifest } from '../adaptive-context.mjs';
4
+
5
+ export function parseContextArguments(commandArguments) {
6
+ const parsedOptions = {
7
+ requestId: 'adhoc-request',
8
+ requestText: '',
9
+ contextFiles: [],
10
+ json: false,
11
+ readStdin: false,
12
+ };
13
+ const requestParts = [];
14
+
15
+ for (let argumentIndex = 0; argumentIndex < commandArguments.length; argumentIndex++) {
16
+ const currentArgument = commandArguments[argumentIndex];
17
+
18
+ if (currentArgument === '--json') {
19
+ parsedOptions.json = true;
20
+ continue;
21
+ }
22
+
23
+ if (currentArgument === '--stdin') {
24
+ parsedOptions.readStdin = true;
25
+ continue;
26
+ }
27
+
28
+ if (currentArgument === '--file') {
29
+ const contextFilePath = commandArguments[argumentIndex + 1];
30
+ if (!contextFilePath || contextFilePath.startsWith('--')) {
31
+ throw new Error('Missing value for --file');
32
+ }
33
+
34
+ parsedOptions.contextFiles.push(contextFilePath);
35
+ argumentIndex++;
36
+ continue;
37
+ }
38
+
39
+ if (currentArgument === '--files') {
40
+ const contextFileList = commandArguments[argumentIndex + 1];
41
+ if (!contextFileList || contextFileList.startsWith('--')) {
42
+ throw new Error('Missing value for --files');
43
+ }
44
+
45
+ parsedOptions.contextFiles.push(
46
+ ...contextFileList
47
+ .split(',')
48
+ .map((contextFilePath) => contextFilePath.trim())
49
+ .filter(Boolean)
50
+ );
51
+ argumentIndex++;
52
+ continue;
53
+ }
54
+
55
+ if (currentArgument === '--request-id') {
56
+ const requestId = commandArguments[argumentIndex + 1];
57
+ if (!requestId || requestId.startsWith('--')) {
58
+ throw new Error('Missing value for --request-id');
59
+ }
60
+
61
+ parsedOptions.requestId = requestId;
62
+ argumentIndex++;
63
+ continue;
64
+ }
65
+
66
+ if (currentArgument.startsWith('--')) {
67
+ throw new Error(`Unknown option: ${currentArgument}`);
68
+ }
69
+
70
+ requestParts.push(currentArgument);
71
+ }
72
+
73
+ parsedOptions.requestText = requestParts.join(' ').trim();
74
+ return parsedOptions;
75
+ }
76
+
77
+ function readRequestFromStdin() {
78
+ return new Promise((resolve, reject) => {
79
+ let requestText = '';
80
+
81
+ stdin.setEncoding('utf8');
82
+ stdin.on('data', (chunk) => {
83
+ requestText += chunk;
84
+ });
85
+ stdin.on('error', reject);
86
+ stdin.on('end', () => {
87
+ resolve(requestText.trim());
88
+ });
89
+ });
90
+ }
91
+
92
+ function formatList(label, values) {
93
+ if (values.length === 0) {
94
+ return [`${label}: none`];
95
+ }
96
+
97
+ return [
98
+ `${label}:`,
99
+ ...values.map((value) => `- ${value}`),
100
+ ];
101
+ }
102
+
103
+ function formatManifestText(manifest) {
104
+ return [
105
+ 'Adaptive Context',
106
+ `requestId: ${manifest.requestId}`,
107
+ `labels: ${manifest.labels.length > 0 ? manifest.labels.join(', ') : 'none'}`,
108
+ `uncertainty: ${manifest.uncertainty}`,
109
+ `budget: ${manifest.budget.status} (${manifest.budget.selectedRuleCount}/${manifest.budget.maxRecommendedRuleCount} recommended rules)`,
110
+ `fallbackRequired: ${manifest.fallbackRequired}`,
111
+ ...formatList('contextFiles', manifest.contextFiles),
112
+ ...formatList('selectedRules', manifest.selectedRules),
113
+ ...formatList('selectedPrompts', manifest.selectedPrompts),
114
+ ...formatList('selectedDocs', manifest.selectedDocs),
115
+ ].join('\n');
116
+ }
117
+
118
+ export async function runContextCommand(commandArguments) {
119
+ const contextOptions = parseContextArguments(commandArguments);
120
+ const requestText = contextOptions.readStdin
121
+ ? await readRequestFromStdin()
122
+ : contextOptions.requestText;
123
+
124
+ if (!requestText) {
125
+ throw new Error('Context request text is required. Pass text as arguments or use --stdin.');
126
+ }
127
+
128
+ const manifest = buildSelectedContextManifest({
129
+ contextFiles: contextOptions.contextFiles,
130
+ requestId: contextOptions.requestId,
131
+ requestText,
132
+ });
133
+
134
+ if (contextOptions.json) {
135
+ console.log(JSON.stringify(manifest, null, 2));
136
+ return;
137
+ }
138
+
139
+ console.log(formatManifestText(manifest));
140
+ }
@@ -45,7 +45,11 @@ import {
45
45
  resolveDetectedSetupDecision,
46
46
  } from '../init-detection-flow.mjs';
47
47
  import { runPreflightChecks } from '../preflight.mjs';
48
- import { createBackup, ensureBackupGitignoreEntry } from '../backup.mjs';
48
+ import {
49
+ createBackup,
50
+ ensureBackupGitignoreEntry,
51
+ ensureRuntimeArtifactGitignoreEntries,
52
+ } from '../backup.mjs';
49
53
  import {
50
54
  runProjectDiscovery,
51
55
  generateProjectDocumentation,
@@ -373,6 +377,12 @@ export async function runInitCommand(targetDirectoryArgument, initOptions = {})
373
377
  if (backupGitignoreResult.status !== 'unchanged') {
374
378
  console.log(`Local backup artifacts ignored in .gitignore (${backupGitignoreResult.entry}).`);
375
379
  }
380
+ const runtimeArtifactGitignoreResult = await ensureRuntimeArtifactGitignoreEntries(resolvedTargetDirectoryPath);
381
+ if (runtimeArtifactGitignoreResult.status !== 'unchanged') {
382
+ console.log(
383
+ `Local runtime artifacts ignored in .gitignore (${runtimeArtifactGitignoreResult.addedEntries.join(', ')}).`
384
+ );
385
+ }
376
386
 
377
387
  await copyGovernanceAssetsToTarget(resolvedTargetDirectoryPath, {
378
388
  includeMcpTemplate: shouldIncludeMcpTemplate,
@@ -565,6 +575,8 @@ export async function runInitCommand(targetDirectoryArgument, initOptions = {})
565
575
  console.log(`- Review thresholds: ${formatBlockingSeverities(selectedPolicyProfile.blockingSeverities)}`);
566
576
  console.log(`- Setup time: ${formatDuration(setupDurationMs)}`);
567
577
  console.log('- Generated files: AGENTS.md, CLAUDE.md, GEMINI.md, .agent-context/, and .agent-context/state/onboarding-report.json');
578
+ console.log('- Default activation cues: Adaptive Context bootstrap, ASCX command wrappers, Compact Natural final replies');
579
+ console.log('- Default response mode: Compact Natural Mode enabled (.agent-context/prompts/compact-natural-mode.md)');
568
580
  if (scaffoldingResult?.bootstrapMode === 'ai-synthesis') {
569
581
  console.log(`- Bootstrap prompts: ${(scaffoldingResult.generatedPromptFileNames || []).length} files generated in .agent-context/prompts/`);
570
582
  if ((scaffoldingResult.materializedFileNames || []).length > 0) {
@@ -587,7 +599,7 @@ export async function runInitCommand(targetDirectoryArgument, initOptions = {})
587
599
  console.log('- Memory continuity policy: disabled (--no-memory-continuity)');
588
600
  }
589
601
  if (isTokenOptimizationEnabled) {
590
- console.log(`- Token optimization policy: enabled for ${selectedTokenAgentName}`);
602
+ console.log(`- Token optimization policy: enabled for ${selectedTokenAgentName} (ASCX command guidance on)`);
591
603
  } else {
592
604
  console.log('- Token optimization policy: disabled (--no-token-optimize)');
593
605
  }