@neikyun/ciel 6.0.2 → 6.0.4

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.
@@ -0,0 +1,509 @@
1
+ "use strict";
2
+ // Ciel -- OpenCode plugin (v6.0.0)
3
+ // Full 16-step pipeline: DOCS -> QUOI -> ASK -> AVEC QUOI -> DIVERGE
4
+ // -> RECHERCHE -> SECURITE -> CODEBASE -> EVALUER -> ASK2
5
+ // -> FAIRE -> ADR -> RELIRE -> PROUVER -> MEMOIRE -> META
6
+ //
7
+ // Injection model:
8
+ // - shell.env -> inject CIEL_SESSION_ID, CIEL_DEPTH, CIEL_MODE
9
+ // - experimental.chat.system.transform -> CIEL WORKFLOW v6 + overlay + state
10
+ // - experimental.chat.messages.transform -> depth classification
11
+ // - session.* events -> tracking, META-CRITIQUER, RELIRE reminders
12
+ // - tool.execute.before -> FAIRE gates reminder + critical file detection
13
+ // - tool.execute.after -> file tracking + RELIRE trigger + map update
14
+ // - experimental.session.compacting -> persist learnings + map
15
+ // - tool helper -> custom ciel-status tool
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ const plugin_1 = require("@opencode-ai/plugin");
18
+ const fs_1 = require("fs");
19
+ const path_1 = require("path");
20
+ // ----- Constants -----
21
+ const CODE_EXT_RE = /\.(kt|java|ts|tsx|js|jsx|py|go|rs|rb|php|cs|cpp|c|swift|scala|vue|svelte|sql)$/i;
22
+ const TEST_FILE_RE = /(\.test\.|\.spec\.|_test\.|_spec\.)(ts|tsx|js|jsx|py|go|rs|rb|php|cs|cpp|c|swift|scala|vue|svelte)$/i;
23
+ const CRITICAL_FILE_RE = /(auth|Auth|security|Security|Route|Service|Controller|Repository|Gateway|Middleware|Proxy|Token|Session|Password|Secret|Payment|Account|Credential)/;
24
+ const CRITICAL_KEYWORD_RE = /\b(auth|authenti|author|jwt|oauth|password|secret|token|session|payment|credit.card|migration.*schema|2fa|mfa|encryption|credential|cookie.*security)\b/i;
25
+ const TRIVIAL_KEYWORD_RE = /\b(rename|typo|copyright|comment|readme|1-line|one.line|fix.typo|spelling)\b/i;
26
+ const CIEL_DIR = ".ciel";
27
+ const MAP_FILE = (0, path_1.join)(CIEL_DIR, "map.json");
28
+ const PARKING_FILE = (0, path_1.join)(CIEL_DIR, "parking.md");
29
+ const LEARNINGS_FILE = (0, path_1.join)(CIEL_DIR, "learnings.md");
30
+ const EXPLORATION_FLAG = (0, path_1.join)(CIEL_DIR, "exploration.active");
31
+ const MEMORY_FILE = (0, path_1.join)(CIEL_DIR, "memory.json");
32
+ // ----- V5 WORKFLOW INJECTION -----
33
+ const CIEL_WORKFLOW_INSTRUCTION = `
34
+ Every response MUST start with depth classification. Then follow the matching pipeline.
35
+
36
+ CLASSIFY: Trivial / Standard / Critical / Spike
37
+
38
+ | Depth | Pipeline |
39
+ |-------|----------|
40
+ | Standard/Critical | DOCS -> QUOI -> ASK -> AVEC QUOI -> DIVERGE -> RECHERCHE -> SECURITE -> CODEBASE -> EVALUER -> ASK2 -> FAIRE -> ADR -> RELIRE -> PROUVER -> MEMOIRE -> META |
41
+ | Trivial | QUOI -> FAIRE -> META |
42
+ | Spike | QUOI -> ASK -> AVEC QUOI -> DIVERGE -> FAIRE (relaxed) -> META |
43
+
44
+ USE the question tool for ASK/ASK2. NEVER skip steps. NEVER code on assumptions.
45
+ `;
46
+ const FAIRE_BEFORE_REMINDER = `
47
+ [CIEL FAIRE GATES -- BEFORE WRITE/EDIT]
48
+ Before executing this write/edit, verify:
49
+ 1. TEST-FIRST (RED): Have you written tests FIRST? If this is source code, a corresponding test file must exist or be created first.
50
+ 2. ALTERNATIVES: Can you justify X over Y? (comment or commit message)
51
+ 3. IDIOMATIC: Are you using the framework's idiomatic pattern? If bypassing, justify why.
52
+ 4. QUALITY: complexity < 15, nesting < 4, functions < 50 lines
53
+ 5. REMOVAL: If deleting code -- who uses it? What replaces it? What degrades?
54
+ 6. BOY-SCOUT: Did you leave the code better than you found it?
55
+ `;
56
+ const META_CRITIQUER = `
57
+ [CIEL META-CRITIQUER -- 30s POST-TASK REFLECTION]
58
+ After completing the task, reflect on:
59
+ (1) Depth match -- etait-ce Trivial/Standard/Critical/Spike correct ?
60
+ (2) Failure mode -- nouveau mode d'echec decouvert ?
61
+ (3) User correction -- l'utilisateur a-t-il corrige quelque chose ? -> persist dans learnings
62
+ (4) Stale branches -- branches a nettoyer ?
63
+ (5) Uncovered issues -- problemes non resolus ?
64
+ (6) Context health -- suggerer /compact si > 50% ?
65
+ (7) Dead code -- code mort introduit ?
66
+ (8) Map update -- la carte du projet (.ciel/map.json) est-elle a jour ?
67
+ (9) Parking -- y a-t-il des decouvertes fortuites a noter dans .ciel/parking.md ?
68
+ (10) Boy-scout -- le code est-il meilleur qu'avant ?
69
+ `;
70
+ // ----- Helpers -----
71
+ function getTestPathForSource(sourcePath) {
72
+ const base = (0, path_1.basename)(sourcePath);
73
+ const dir = (0, path_1.dirname)(sourcePath);
74
+ const nameWithoutExt = base.replace(/\.[^.]+$/, "");
75
+ const ext = base.match(/\.[^.]+$/)?.[0] ?? "";
76
+ const candidates = [
77
+ (0, path_1.join)(dir, `${nameWithoutExt}.test${ext}`),
78
+ (0, path_1.join)(dir, `${nameWithoutExt}.spec${ext}`),
79
+ (0, path_1.join)(dir, `${nameWithoutExt}_test${ext}`),
80
+ (0, path_1.join)(dir, `${nameWithoutExt}_spec${ext}`),
81
+ (0, path_1.join)(dir, "__tests__", `${nameWithoutExt}${ext}`),
82
+ (0, path_1.join)(dir, "test", `${nameWithoutExt}${ext}`),
83
+ (0, path_1.join)(dir, "tests", `${nameWithoutExt}${ext}`),
84
+ ];
85
+ if (ext === ".ts" || ext === ".tsx") {
86
+ candidates.push((0, path_1.join)(dir, `${nameWithoutExt}.test.js`));
87
+ candidates.push((0, path_1.join)(dir, `${nameWithoutExt}.spec.js`));
88
+ }
89
+ return candidates;
90
+ }
91
+ function sourceFileHasTest(sourcePath) {
92
+ const candidates = getTestPathForSource(sourcePath);
93
+ for (const candidate of candidates) {
94
+ if ((0, fs_1.existsSync)(candidate))
95
+ return true;
96
+ }
97
+ return false;
98
+ }
99
+ function isTestFile(filePath) {
100
+ return TEST_FILE_RE.test(filePath);
101
+ }
102
+ function isSourceFile(filePath) {
103
+ return CODE_EXT_RE.test(filePath) && !isTestFile(filePath);
104
+ }
105
+ function isSpikeMode() {
106
+ return (0, fs_1.existsSync)(EXPLORATION_FLAG);
107
+ }
108
+ function ensureCielDir() {
109
+ if (!(0, fs_1.existsSync)(CIEL_DIR)) {
110
+ (0, fs_1.mkdirSync)(CIEL_DIR, { recursive: true });
111
+ }
112
+ }
113
+ function writeParkingEntry(entry) {
114
+ ensureCielDir();
115
+ const timestamp = new Date().toISOString().split("T")[0];
116
+ const formatted = `- [${timestamp}] ${entry}\n`;
117
+ try {
118
+ const existing = (0, fs_1.existsSync)(PARKING_FILE) ? (0, fs_1.readFileSync)(PARKING_FILE, "utf-8") : "";
119
+ const header = "# Ciel Parking Lot -- Decouvertes fortuites\n\n";
120
+ const content = existing.startsWith("#") ? existing : header + existing;
121
+ (0, fs_1.writeFileSync)(PARKING_FILE, content + formatted, "utf-8");
122
+ }
123
+ catch {
124
+ // silent
125
+ }
126
+ }
127
+ // ----- Plugin -----
128
+ const ciel = async ({ client }) => {
129
+ const writtenFiles = new Set();
130
+ const MAX_TRACKED_FILES = 100;
131
+ let relireSticky = false;
132
+ let lastDepthHint = null;
133
+ let overlayContent = null;
134
+ let faireBlocked = null;
135
+ let sessionId = "unknown";
136
+ let taskCount = 0;
137
+ let readDocsAttempted = false;
138
+ let askWindowUsed = false;
139
+ return {
140
+ // ----- CUSTOM TOOLS -----
141
+ tool: {
142
+ "ciel-status": (0, plugin_1.tool)({
143
+ description: "Shows current Ciel session state: depth classification, files changed, RELIRE status, FAIRE gate state, spike mode. Use when user asks 'what is the Ciel status' or 'show Ciel state'.",
144
+ args: {},
145
+ async execute(_args, _context) {
146
+ const changed = Array.from(writtenFiles);
147
+ return JSON.stringify({
148
+ sessionId,
149
+ depthHint: lastDepthHint,
150
+ filesChanged: changed.length,
151
+ files: changed.slice(0, 10),
152
+ relireRequired: relireSticky,
153
+ spikeMode: isSpikeMode(),
154
+ taskCount,
155
+ askWindowUsed,
156
+ readDocsAttempted,
157
+ faireBlocked: faireBlocked ? {
158
+ file: faireBlocked.filePath,
159
+ gate: faireBlocked.gate,
160
+ } : null,
161
+ overlayLoaded: overlayContent != null,
162
+ }, null, 2);
163
+ },
164
+ }),
165
+ },
166
+ // ----- SHELL ENV -----
167
+ "shell.env": async (_input, output) => {
168
+ output.env.CIEL_SESSION_ID = sessionId;
169
+ output.env.CIEL_DEPTH = lastDepthHint ?? "unclassified";
170
+ output.env.CIEL_MODE = isSpikeMode() ? "spike" : "standard";
171
+ },
172
+ // ----- EVENTS -----
173
+ event: async ({ event }) => {
174
+ // Log all event types for debugging session ID detection
175
+ if (event.type && !event.type.startsWith("tool.") && event.type !== "session.diff") {
176
+ await client.app.log({
177
+ body: { service: "ciel", level: "debug", message: `Event: ${event.type}` },
178
+ });
179
+ }
180
+ if (event.type === "session.created" || event.type === "session.updated" || event.type === "session.status") {
181
+ // Try multiple locations for session ID
182
+ const evt = event;
183
+ const rawId = evt?.info?.id ?? evt?.sessionID ?? evt?.sessionId ?? evt?.id ?? evt?.session?.id ?? `s-${Date.now().toString(36)}`;
184
+ sessionId = typeof rawId === "string" ? rawId.slice(0, 8) : "unknown";
185
+ taskCount = 0;
186
+ }
187
+ // Always ensure .ciel/ directory exists (runs on ANY session event)
188
+ if (event.type === "session.created" || event.type === "session.updated" || event.type === "session.status" || event.type === "session.diff") {
189
+ // Initialize .ciel/ directory if missing
190
+ ensureCielDir();
191
+ if (!(0, fs_1.existsSync)(MAP_FILE)) {
192
+ (0, fs_1.writeFileSync)(MAP_FILE, JSON.stringify({ modules: [], lastUpdated: new Date().toISOString() }, null, 2), "utf-8");
193
+ }
194
+ if (!(0, fs_1.existsSync)(MEMORY_FILE)) {
195
+ (0, fs_1.writeFileSync)(MEMORY_FILE, "{}", "utf-8");
196
+ }
197
+ if (!(0, fs_1.existsSync)(PARKING_FILE)) {
198
+ (0, fs_1.writeFileSync)(PARKING_FILE, "# Ciel Parking Lot -- Decouvertes fortuites\n\n", "utf-8");
199
+ }
200
+ await client.app.log({
201
+ body: { service: "ciel", level: "info", message: `Session ${sessionId} started` },
202
+ });
203
+ // Load overlay
204
+ if ((0, fs_1.existsSync)("./ciel-overlay.md")) {
205
+ try {
206
+ const rawOverlay = (0, fs_1.readFileSync)("./ciel-overlay.md", "utf-8");
207
+ overlayContent = rawOverlay.replace(/##\s*\S*sensitive[:\s]*true\S*\s*\n([\s\S]*?)(?=\n##\s|\n*$)/gi, "## [REDACTED -- sensitive section]\n");
208
+ }
209
+ catch {
210
+ // silent
211
+ }
212
+ }
213
+ writtenFiles.clear();
214
+ relireSticky = false;
215
+ lastDepthHint = null;
216
+ faireBlocked = null;
217
+ readDocsAttempted = false;
218
+ askWindowUsed = false;
219
+ }
220
+ if (event.type === "session.diff") {
221
+ const diffs = event.diff ?? [];
222
+ for (const fileDiff of diffs) {
223
+ const path = fileDiff?.path ?? "";
224
+ if (CODE_EXT_RE.test(path)) {
225
+ if (writtenFiles.size >= MAX_TRACKED_FILES) {
226
+ const firstKey = writtenFiles.values().next().value;
227
+ writtenFiles.delete(firstKey);
228
+ }
229
+ writtenFiles.add(path);
230
+ if (writtenFiles.size >= 5 || CRITICAL_FILE_RE.test(path)) {
231
+ relireSticky = true;
232
+ }
233
+ }
234
+ }
235
+ }
236
+ if (event.type === "session.idle") {
237
+ taskCount++;
238
+ lastDepthHint = `CIEL STOP -- META-CRITIQUER: (1) depth match? (2) failure mode? (3) user correction -> learnings? (4) stale branches? (8) map update? (9) parking note?`;
239
+ relireSticky = true;
240
+ faireBlocked = null;
241
+ }
242
+ if (event.type === "session.deleted") {
243
+ const rawId = event.sessionID ?? event.info?.id ?? "unknown";
244
+ const sid = typeof rawId === "string" ? rawId.slice(0, 8) : "unknown";
245
+ const isChild = event.parentSessionId != null;
246
+ await client.app.log({
247
+ body: { service: "ciel", level: "info", message: `Session ${sid} deleted${isChild ? " (subagent child)" : ""}` },
248
+ });
249
+ }
250
+ if (event.type === "session.error") {
251
+ const errorName = event.error?.name ?? "UnknownError";
252
+ const errorMessage = event.error?.message ?? "";
253
+ if (errorName === "ProviderAuthError" || errorName === "MessageAbortedError") {
254
+ await client.app.log({
255
+ body: { service: "ciel", level: "error", message: `${errorName}: ${errorMessage}` },
256
+ });
257
+ }
258
+ }
259
+ if (event.type === "session.compacted") {
260
+ await client.app.log({
261
+ body: { service: "ciel", level: "info", message: `Session ${sessionId} compacted -- state preserved` },
262
+ });
263
+ }
264
+ },
265
+ // ----- SYSTEM TRANSFORM -----
266
+ "experimental.chat.system.transform": async (_input, output) => {
267
+ if (!Array.isArray(output?.system))
268
+ return;
269
+ // Mandatory workflow injection (first -- highest priority)
270
+ output.system.push(CIEL_WORKFLOW_INSTRUCTION);
271
+ // Overlay injection
272
+ if (overlayContent) {
273
+ output.system.push(`Project Overlay:\n${overlayContent}`);
274
+ }
275
+ // Load .ciel/map.json if it exists
276
+ if ((0, fs_1.existsSync)(MAP_FILE)) {
277
+ try {
278
+ const mapContent = (0, fs_1.readFileSync)(MAP_FILE, "utf-8");
279
+ output.system.push(`Project Map (.ciel/map.json):\n${mapContent}`);
280
+ }
281
+ catch {
282
+ // silent
283
+ }
284
+ }
285
+ // Load .ciel/memory.json if it exists
286
+ if ((0, fs_1.existsSync)(MEMORY_FILE)) {
287
+ try {
288
+ const memoryContent = (0, fs_1.readFileSync)(MEMORY_FILE, "utf-8");
289
+ output.system.push(`Session Memory (.ciel/memory.json):\n${memoryContent}`);
290
+ }
291
+ catch {
292
+ // silent
293
+ }
294
+ }
295
+ // SPIKE mode indicator
296
+ if (isSpikeMode()) {
297
+ output.system.push("[CIEL SPIKE MODE] Exploration/prototype mode active. Quality gates are ASSOUPLIES.\n" +
298
+ "This code is experimental. FIXME/TODO markers required. Must be refactored properly after.\n" +
299
+ "To exit spike mode, remove .ciel/exploration.active");
300
+ }
301
+ // Depth hint
302
+ if (lastDepthHint) {
303
+ output.system.push(lastDepthHint);
304
+ }
305
+ // META-CRITIQUER always injected
306
+ output.system.push(META_CRITIQUER);
307
+ // FAIRE gate blocked
308
+ if (faireBlocked) {
309
+ output.system.push(`[CIEL FAIRE GATE TRIGGERED] You just wrote ${faireBlocked.filePath} without a corresponding test file.\n\n` +
310
+ `This means you skipped the Ciel workflow. You MUST now:\n` +
311
+ `1. Classify depth (Trivial/Standard/Critical/Spike)\n` +
312
+ `2. Follow the pipeline: DOCS -> QUOI -> ASK -> AVEC QUOI -> ... -> FAIRE -> RELIRE -> PROUVER -> MEMOIRE -> META\n` +
313
+ `3. Dispatch subagents if required (@ciel-researcher, @ciel-explorer)\n` +
314
+ `4. Write the test file FIRST, then implement\n\n` +
315
+ `Candidates checked: ${faireBlocked.candidates.slice(0, 3).join(", ")}\n\n` +
316
+ `Do NOT continue writing source code until tests exist. Follow the full Ciel pipeline.`);
317
+ }
318
+ // RELIRE sticky notice
319
+ if (relireSticky) {
320
+ const changed = Array.from(writtenFiles);
321
+ output.system.push(`[CIEL RELIRE REQUIRED] ${changed.length} files changed. Dispatch @ciel-critic MODE=RELIRE -- 3 RISQUES + FIX/ACCEPT/DEFER.`);
322
+ }
323
+ },
324
+ // ----- MESSAGES TRANSFORM (depth classification) -----
325
+ // Read the most recent user message and classify depth.
326
+ // Does NOT modify the messages array -- splicing synthetic messages
327
+ // causes "U.parts.length undefined" crashes in the OpenCode SDK
328
+ // (the injected message lacks the expected message shape).
329
+ // Depth hints are injected via experimental.chat.system.transform instead.
330
+ "experimental.chat.messages.transform": async (_input, output) => {
331
+ const msgs = output?.messages;
332
+ if (!Array.isArray(msgs) || msgs.length === 0)
333
+ return;
334
+ // Find the most recent user text part.
335
+ let prompt = "";
336
+ for (let i = msgs.length - 1; i >= 0 && !prompt; i--) {
337
+ const m = msgs[i];
338
+ if (m?.info?.role !== "user")
339
+ continue;
340
+ const parts = m?.parts;
341
+ if (!Array.isArray(parts))
342
+ continue;
343
+ for (let j = parts.length - 1; j >= 0; j--) {
344
+ const p = parts[j];
345
+ if (p?.type === "text" && typeof p.text === "string") {
346
+ prompt = p.text;
347
+ break;
348
+ }
349
+ }
350
+ }
351
+ if (!prompt)
352
+ return;
353
+ let depth = null;
354
+ let reason = "";
355
+ if (CRITICAL_KEYWORD_RE.test(prompt)) {
356
+ depth = "Critical";
357
+ reason = "auth/security/payment keyword detected";
358
+ }
359
+ else if (TRIVIAL_KEYWORD_RE.test(prompt)) {
360
+ depth = "Trivial";
361
+ reason = "rename/typo/docs keyword detected";
362
+ }
363
+ lastDepthHint = depth
364
+ ? `[CIEL] Depth: ${depth} (${reason}). Route the pipeline accordingly.`
365
+ : null;
366
+ },
367
+ // ----- COMPACTING (cross-session memory -- persist automatically) -----
368
+ "experimental.session.compacting": async (_input, output) => {
369
+ // Persist .ciel/memory.json with current state
370
+ try {
371
+ ensureCielDir();
372
+ const memory = {
373
+ sessionId,
374
+ depthHint: lastDepthHint,
375
+ filesChanged: Array.from(writtenFiles).slice(-20),
376
+ taskCount,
377
+ timestamp: new Date().toISOString(),
378
+ };
379
+ (0, fs_1.writeFileSync)(MEMORY_FILE, JSON.stringify(memory, null, 2), "utf-8");
380
+ }
381
+ catch {
382
+ // silent
383
+ }
384
+ // Inject context for the LLM to update learnings, map, and parking
385
+ output.context.push("CIEL PRE-COMPACT -- Persist if needed: (1) user corrections -> .ciel/learnings.md, " +
386
+ "(2) project map updates -> .ciel/map.json, " +
387
+ "(3) fortuitous discoveries -> .ciel/parking.md. " +
388
+ "Memory already saved at .ciel/memory.json");
389
+ },
390
+ // ----- BEFORE HOOK -- FAIRE gates -----
391
+ "tool.execute.before": async (input, output) => {
392
+ if (!["write", "edit"].includes(input.tool))
393
+ return;
394
+ const filePath = output?.args?.filePath ?? "";
395
+ if (!filePath || !CODE_EXT_RE.test(filePath))
396
+ return;
397
+ // Skip for the plugin itself and test files
398
+ if (filePath.includes("ciel.ts") || isTestFile(filePath))
399
+ return;
400
+ // Gate 1: TEST-FIRST (RED) -- only block if NOT in SPIKE mode
401
+ if (isSourceFile(filePath) && !sourceFileHasTest(filePath) && !isSpikeMode()) {
402
+ const testCandidates = getTestPathForSource(filePath);
403
+ faireBlocked = { filePath, gate: "test-first", candidates: testCandidates };
404
+ }
405
+ else {
406
+ faireBlocked = null;
407
+ }
408
+ // Gate 2: CRITICAL FILE WARNING
409
+ if (CRITICAL_FILE_RE.test(filePath)) {
410
+ await client.app.log({
411
+ body: { service: "ciel", level: "warn", message: `CRITICAL FILE: ${filePath} -- stride-analyzer + security-regression-check required` },
412
+ });
413
+ }
414
+ // Gate 3: PARKING LOT -- detect if this is a tangential discovery
415
+ if (filePath.includes("parking") || filePath.includes("FIXME") || filePath.includes("TODO")) {
416
+ writeParkingEntry(`Tangential file noted during task: ${filePath}`);
417
+ }
418
+ // Inject FAIRE reminder into the tool output
419
+ const faireReminder = FAIRE_BEFORE_REMINDER.trim();
420
+ if (typeof output?.output === "string") {
421
+ output.output = faireReminder + "\n" + output.output;
422
+ }
423
+ else if (output) {
424
+ output.output = faireReminder;
425
+ }
426
+ },
427
+ // ----- AFTER HOOK -- file tracking + map update -----
428
+ "tool.execute.after": async (input, output) => {
429
+ const toolName = input?.tool ?? "";
430
+ // Track ASK window usage for any question tool call
431
+ if (toolName === "question") {
432
+ askWindowUsed = true;
433
+ return; // question tool has no file path, nothing else to do
434
+ }
435
+ // Only process write/edit tools for file tracking
436
+ if (!["write", "edit"].includes(toolName))
437
+ return;
438
+ const filePath = output?.metadata?.filepath ??
439
+ output?.metadata?.filediff?.file ??
440
+ output?.args?.filePath ??
441
+ "";
442
+ if (!filePath || !CODE_EXT_RE.test(filePath))
443
+ return;
444
+ if (writtenFiles.size >= MAX_TRACKED_FILES) {
445
+ const firstKey = writtenFiles.values().next().value;
446
+ writtenFiles.delete(firstKey);
447
+ }
448
+ writtenFiles.add(filePath);
449
+ if (writtenFiles.size >= 5 || CRITICAL_FILE_RE.test(filePath)) {
450
+ relireSticky = true;
451
+ }
452
+ const isCritical = CRITICAL_FILE_RE.test(filePath);
453
+ const spike = isSpikeMode();
454
+ const reminder = isCritical
455
+ ? `\n\n[CIEL CRITIQUE] ${filePath} -- FAIRE gates + stride-analyzer + test-first (RED). Dispatch @ciel-critic MODE=RELIRE.`
456
+ : spike
457
+ ? `\n\n[CIEL SPIKE] ${filePath} -- gates assouplies. Marquer comme experimental (FIXME/TODO).`
458
+ : `\n\n[CIEL] ${filePath} -- FAIRE gates: alternatives, idiomatic, test-first, boy-scout.`;
459
+ if (typeof output?.output === "string") {
460
+ output.output += reminder;
461
+ }
462
+ else if (output) {
463
+ output.output = reminder.trimStart();
464
+ }
465
+ // Track DOCS phase attempt
466
+ if (!readDocsAttempted && (filePath.endsWith("README.md") || filePath.endsWith("AGENTS.md") || filePath.endsWith("CLAUDE.md") || filePath.includes("ciel-overlay") || filePath.endsWith("docs/"))) {
467
+ readDocsAttempted = true;
468
+ }
469
+ // Update .ciel/map.json with modules discovered during exploration
470
+ if (filePath.endsWith(".ts") || filePath.endsWith(".tsx") || filePath.endsWith(".js") || filePath.endsWith(".py") || filePath.endsWith(".go") || filePath.endsWith(".rs")) {
471
+ try {
472
+ ensureCielDir();
473
+ let map = { modules: [], lastUpdated: new Date().toISOString() };
474
+ if ((0, fs_1.existsSync)(MAP_FILE)) {
475
+ map = JSON.parse((0, fs_1.readFileSync)(MAP_FILE, "utf-8"));
476
+ }
477
+ // Simple heuristic: the directory 2 levels deep is a module
478
+ const parts = filePath.replace(/^\.\//, "").split("/");
479
+ if (parts.length >= 2) {
480
+ const moduleName = parts[parts.length - 2];
481
+ const existingModule = map.modules?.find((m) => m.name === moduleName);
482
+ if (!existingModule) {
483
+ map.modules = map.modules || [];
484
+ map.modules.push({
485
+ name: moduleName,
486
+ path: parts.slice(0, -1).join("/"),
487
+ key_files: [{ path: filePath, responsibility: "auto-detected" }],
488
+ });
489
+ }
490
+ else {
491
+ const existingFile = existingModule.key_files?.find((f) => f.path === filePath);
492
+ if (!existingFile) {
493
+ existingModule.key_files = existingModule.key_files || [];
494
+ existingModule.key_files.push({ path: filePath, responsibility: "auto-detected" });
495
+ }
496
+ }
497
+ map.lastUpdated = new Date().toISOString();
498
+ (0, fs_1.writeFileSync)(MAP_FILE, JSON.stringify(map, null, 2), "utf-8");
499
+ }
500
+ }
501
+ catch {
502
+ // silent
503
+ }
504
+ }
505
+ },
506
+ };
507
+ };
508
+ exports.default = ciel;
509
+ //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"check.d.ts","sourceRoot":"","sources":["../../src/cli/check.ts"],"names":[],"mappings":"AAqCA,wBAAsB,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CA8B9C"}
1
+ {"version":3,"file":"check.d.ts","sourceRoot":"","sources":["../../src/cli/check.ts"],"names":[],"mappings":"AAsCA,wBAAsB,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CAuC9C"}
package/dist/cli/check.js CHANGED
@@ -1,18 +1,19 @@
1
1
  "use strict";
2
- // Check command — compare local version against GitHub
2
+ // Check command — compare local version against NPM registry
3
+ // (NPM is the canonical distribution channel for Ciel v6+)
3
4
  Object.defineProperty(exports, "__esModule", { value: true });
4
5
  exports.runCheck = runCheck;
5
6
  const https_1 = require("https");
6
7
  const utils_1 = require("./utils");
7
8
  const version_1 = require("./version");
8
- const GITHUB_RAW = "https://raw.githubusercontent.com/KaosKyun/Ciel/main";
9
+ const NPM_REGISTRY = "https://registry.npmjs.org/@neikyun/ciel/latest";
9
10
  const CIEL_VERSION = (0, version_1.getVersion)();
10
11
  function fetchUrl(url) {
11
12
  return new Promise((resolve, reject) => {
12
- (0, https_1.get)(url, (res) => {
13
+ (0, https_1.get)(url, { headers: { Accept: "application/json" } }, (res) => {
13
14
  let data = "";
14
15
  res.on("data", (chunk) => (data += chunk));
15
- res.on("end", () => resolve(data.trim()));
16
+ res.on("end", () => resolve(data));
16
17
  }).on("error", reject);
17
18
  });
18
19
  }
@@ -37,9 +38,11 @@ function compareVersions(a, b) {
37
38
  }
38
39
  async function runCheck() {
39
40
  try {
40
- const remoteVersion = await fetchUrl(`${GITHUB_RAW}/VERSION`);
41
+ const raw = await fetchUrl(NPM_REGISTRY);
42
+ const pkg = JSON.parse(raw);
43
+ const remoteVersion = pkg.version;
41
44
  if (!remoteVersion) {
42
- (0, utils_1.err)("Could not fetch remote version from GitHub.");
45
+ (0, utils_1.err)("Could not fetch latest version from NPM registry.");
43
46
  (0, utils_1.err)("Check your internet connection.");
44
47
  process.exit(2);
45
48
  }
@@ -50,12 +53,18 @@ async function runCheck() {
50
53
  }
51
54
  if (cmp < 0) {
52
55
  // Remote is newer
53
- console.log(` Update available: v${CIEL_VERSION} → ${remoteVersion}`);
54
- console.log(" Run 'npx ciel update' to upgrade.");
56
+ console.log(` Update available: v${CIEL_VERSION} → v${remoteVersion}`);
57
+ console.log("");
58
+ console.log(" If installed globally:");
59
+ console.log(" npm update -g @neikyun/ciel");
60
+ console.log(" ciel update");
61
+ console.log("");
62
+ console.log(" If installed in project:");
63
+ console.log(" npm update @neikyun/ciel");
55
64
  process.exit(0);
56
65
  }
57
66
  // Local is newer (dev mode)
58
- (0, utils_1.say)(`Ciel v${CIEL_VERSION} (ahead of remote v${remoteVersion} — dev mode)`);
67
+ (0, utils_1.say)(`Ciel v${CIEL_VERSION} (ahead of npm v${remoteVersion} — dev mode)`);
59
68
  process.exit(0);
60
69
  }
61
70
  catch (error) {
@@ -1 +1 @@
1
- {"version":3,"file":"check.js","sourceRoot":"","sources":["../../src/cli/check.ts"],"names":[],"mappings":";AAAA,uDAAuD;;AAqCvD,4BA8BC;AAjED,iCAAwC;AACxC,mCAAuC;AACvC,uCAAuC;AAEvC,MAAM,UAAU,GAAG,sDAAsD,CAAC;AAC1E,MAAM,YAAY,GAAG,IAAA,oBAAU,GAAE,CAAC;AAElC,SAAS,QAAQ,CAAC,GAAW;IAC3B,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,IAAA,WAAQ,EAAC,GAAG,EAAE,CAAC,GAAG,EAAE,EAAE;YACpB,IAAI,IAAI,GAAG,EAAE,CAAC;YACd,GAAG,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE,CAAC,CAAC,IAAI,IAAI,KAAK,CAAC,CAAC,CAAC;YACnD,GAAG,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;QAC5C,CAAC,CAAC,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IACzB,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;;;GAKG;AACH,SAAS,eAAe,CAAC,CAAS,EAAE,CAAS;IAC3C,MAAM,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACpC,MAAM,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACpC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QACxD,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QACtB,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QACtB,IAAI,EAAE,GAAG,EAAE;YAAE,OAAO,CAAC,CAAC;QACtB,IAAI,EAAE,GAAG,EAAE;YAAE,OAAO,CAAC,CAAC,CAAC;IACzB,CAAC;IACD,OAAO,CAAC,CAAC;AACX,CAAC;AAEM,KAAK,UAAU,QAAQ;IAC5B,IAAI,CAAC;QACH,MAAM,aAAa,GAAG,MAAM,QAAQ,CAAC,GAAG,UAAU,UAAU,CAAC,CAAC;QAC9D,IAAI,CAAC,aAAa,EAAE,CAAC;YACnB,IAAA,WAAG,EAAC,6CAA6C,CAAC,CAAC;YACnD,IAAA,WAAG,EAAC,iCAAiC,CAAC,CAAC;YACvC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QAED,MAAM,GAAG,GAAG,eAAe,CAAC,YAAY,EAAE,aAAa,CAAC,CAAC;QAEzD,IAAI,GAAG,KAAK,CAAC,EAAE,CAAC;YACd,IAAA,UAAE,EAAC,SAAS,YAAY,iBAAiB,CAAC,CAAC;YAC3C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QAED,IAAI,GAAG,GAAG,CAAC,EAAE,CAAC;YACZ,kBAAkB;YAClB,OAAO,CAAC,GAAG,CAAC,wBAAwB,YAAY,MAAM,aAAa,EAAE,CAAC,CAAC;YACvE,OAAO,CAAC,GAAG,CAAC,qCAAqC,CAAC,CAAC;YACnD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QAED,4BAA4B;QAC5B,IAAA,WAAG,EAAC,SAAS,YAAY,sBAAsB,aAAa,cAAc,CAAC,CAAC;QAC5E,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAAC,OAAO,KAAU,EAAE,CAAC;QACpB,IAAA,WAAG,EAAC,kBAAkB,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;QACvC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"check.js","sourceRoot":"","sources":["../../src/cli/check.ts"],"names":[],"mappings":"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"}
package/dist/cli/index.js CHANGED
@@ -26,9 +26,9 @@ USAGE:
26
26
 
27
27
  COMMANDS:
28
28
  init Install Ciel in the current project (default)
29
- update Force reinstall even if already installed
29
+ update Force reinstall (run after npm update -g @neikyun/ciel)
30
30
  uninstall Remove all Ciel files from the project
31
- check Check GitHub for a newer version
31
+ check Check NPM for a newer version
32
32
 
33
33
  OPTIONS:
34
34
  -y, --yes Skip confirmation prompt (non-interactive)
@@ -1 +1 @@
1
- {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/cli/init.ts"],"names":[],"mappings":"AAWA,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,OAAO,CAAC;IACb,KAAK,EAAE,OAAO,CAAC;IACf,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AA+HD,wBAAsB,OAAO,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CA0FjE"}
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/cli/init.ts"],"names":[],"mappings":"AAWA,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,OAAO,CAAC;IACb,KAAK,EAAE,OAAO,CAAC;IACf,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAmID,wBAAsB,OAAO,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CA0FjE"}
package/dist/cli/init.js CHANGED
@@ -34,13 +34,15 @@ function resolveSourceDir() {
34
34
  (0, path_1.join)(process.cwd(), "../.."),
35
35
  // Dev: running from packages/ (sub-workspace)
36
36
  (0, path_1.join)(process.cwd(), ".."),
37
- // npm: bundled assets in package
37
+ // npm: bundled assets (compiled CLI: __dirname = dist/cli/)
38
+ (0, path_1.join)(__dirname, "..", "..", "assets"),
39
+ // npm: installed globally (__dirname = dist/cli/ → go up to root)
40
+ (0, path_1.join)(__dirname, "..", ".."),
41
+ // npm: one level from assets (if __dirname is assets/)
38
42
  (0, path_1.join)(__dirname, "..", "assets"),
39
- // npm: installed globally
43
+ // npm: npx cache deeper
40
44
  (0, path_1.join)(__dirname, ".."),
41
- // npm: one more level up (npx cache)
42
45
  (0, path_1.join)(__dirname, "../.."),
43
- // npm: two levels up (npx cache, deeper)
44
46
  (0, path_1.join)(__dirname, "../../.."),
45
47
  ];
46
48
  for (const dir of candidates) {
@@ -57,6 +59,8 @@ function resolveSourceDir() {
57
59
  async function downloadTemplatesToTemp() {
58
60
  const tmpDir = (0, fs_1.mkdtempSync)((0, path_1.join)((0, os_1.tmpdir)(), "ciel-templates-"));
59
61
  const templatePaths = [
62
+ // Compiled plugin JS (for local reference, no node_modules needed)
63
+ "platforms/opencode/.opencode/plugins/ciel.js",
60
64
  // OpenCode agents
61
65
  "platforms/opencode/.opencode/agents/ciel.md",
62
66
  "platforms/opencode/.opencode/agents/ciel-researcher.md",