gaslighting-engine 0.2.2 → 0.3.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 (36) hide show
  1. package/.agents/prompts/gaslighting.md +2 -1
  2. package/.agents/skills/gaslighting/SKILL.md +4 -1
  3. package/.codex/prompts/gaslighting.md +2 -1
  4. package/.codex/skills/gaslighting/SKILL.md +4 -1
  5. package/README.md +50 -1
  6. package/dist/cli.js +41 -1
  7. package/dist/commands/cockpit.js +197 -0
  8. package/dist/commands/doctor.js +1 -0
  9. package/dist/core/cockpitHtml.js +598 -0
  10. package/dist/core/codexRuntime.js +103 -0
  11. package/dist/core/generateDocs.js +5 -3
  12. package/dist/core/generateOtherMarkdown.js +68 -4
  13. package/dist/index.js +1 -1
  14. package/dist/utils/logger.js +1 -1
  15. package/dist/version.js +1 -1
  16. package/docs/codex-usage.md +24 -4
  17. package/docs/examples.md +3 -0
  18. package/examples/ecommerce/.codex/prompts/gaslighting.md +2 -1
  19. package/examples/ecommerce/.codex/skills/gaslighting/SKILL.md +4 -1
  20. package/examples/ecommerce/.gaslighting/AGENTS.md +3 -1
  21. package/examples/ecommerce/.gaslighting/AGENT_RUNTIME.md +54 -0
  22. package/examples/ecommerce/.gaslighting/CODEX_PROMPT.md +2 -1
  23. package/examples/ecommerce/AGENTS.md +3 -2
  24. package/examples/hospital-homepage/.codex/prompts/gaslighting.md +2 -1
  25. package/examples/hospital-homepage/.codex/skills/gaslighting/SKILL.md +4 -1
  26. package/examples/hospital-homepage/.gaslighting/AGENTS.md +3 -1
  27. package/examples/hospital-homepage/.gaslighting/AGENT_RUNTIME.md +54 -0
  28. package/examples/hospital-homepage/.gaslighting/CODEX_PROMPT.md +2 -1
  29. package/examples/hospital-homepage/AGENTS.md +3 -2
  30. package/examples/landing-page/.codex/prompts/gaslighting.md +2 -1
  31. package/examples/landing-page/.codex/skills/gaslighting/SKILL.md +4 -1
  32. package/examples/landing-page/.gaslighting/AGENTS.md +3 -1
  33. package/examples/landing-page/.gaslighting/AGENT_RUNTIME.md +54 -0
  34. package/examples/landing-page/.gaslighting/CODEX_PROMPT.md +2 -1
  35. package/examples/landing-page/AGENTS.md +3 -2
  36. package/package.json +1 -1
@@ -10,7 +10,8 @@ Read the Gaslighting-engine project-control files before doing any work:
10
10
  6. `.gaslighting/DECISION_LOG.md`
11
11
  7. `AGENTS.md`
12
12
  8. `.gaslighting/MEMORY.md`
13
- 9. `.gaslighting/PROJECT_CARE.md`
13
+ 9. `.gaslighting/AGENT_RUNTIME.md`
14
+ 10. `.gaslighting/PROJECT_CARE.md`
14
15
 
15
16
  Then execute the user's requested implementation.
16
17
 
@@ -23,7 +23,8 @@ Instead:
23
23
  10. Generate `.gaslighting/DECISION_LOG.md`.
24
24
  11. Generate `.gaslighting/STACK_POLICY.md`.
25
25
  12. Generate or update root `AGENTS.md`.
26
- 13. Generate `.gaslighting/PROJECT_CARE.md`.
26
+ 13. Generate `.gaslighting/AGENT_RUNTIME.md`.
27
+ 14. Generate `.gaslighting/PROJECT_CARE.md`.
27
28
 
28
29
  ## Hardcore Discipline Rule
29
30
 
@@ -92,6 +93,8 @@ Keep main project-control files in `.gaslighting/` and keep only the Codex point
92
93
 
93
94
  Use `.gaslighting/PROJECT_CARE.md` to track Git/GitHub/domain/deployment/launch risks without blocking implementation.
94
95
 
96
+ Use `.gaslighting/AGENT_RUNTIME.md` to define the execution loop Codex must follow after the docs are generated.
97
+
95
98
  Do not only explain.
96
99
 
97
100
  Do not produce a plan without files.
@@ -10,7 +10,8 @@ Read the Gaslighting-engine project-control files before doing any work:
10
10
  6. `.gaslighting/DECISION_LOG.md`
11
11
  7. `AGENTS.md`
12
12
  8. `.gaslighting/MEMORY.md`
13
- 9. `.gaslighting/PROJECT_CARE.md`
13
+ 9. `.gaslighting/AGENT_RUNTIME.md`
14
+ 10. `.gaslighting/PROJECT_CARE.md`
14
15
 
15
16
  Then execute the user's requested implementation.
16
17
 
@@ -23,7 +23,8 @@ Instead:
23
23
  10. Generate `.gaslighting/DECISION_LOG.md`.
24
24
  11. Generate `.gaslighting/STACK_POLICY.md`.
25
25
  12. Generate or update root `AGENTS.md`.
26
- 13. Generate `.gaslighting/PROJECT_CARE.md`.
26
+ 13. Generate `.gaslighting/AGENT_RUNTIME.md`.
27
+ 14. Generate `.gaslighting/PROJECT_CARE.md`.
27
28
 
28
29
  ## Hardcore Discipline Rule
29
30
 
@@ -92,6 +93,8 @@ Keep main project-control files in `.gaslighting/` and keep only the Codex point
92
93
 
93
94
  Use `.gaslighting/PROJECT_CARE.md` to track Git/GitHub/domain/deployment/launch risks without blocking implementation.
94
95
 
96
+ Use `.gaslighting/AGENT_RUNTIME.md` to define the execution loop Codex must follow after the docs are generated.
97
+
95
98
  Do not only explain.
96
99
 
97
100
  Do not produce a plan without files.
package/README.md CHANGED
@@ -30,6 +30,7 @@ It generates:
30
30
  - `.gaslighting/MISSING_INFO.md`
31
31
  - `.gaslighting/DECISION_LOG.md`
32
32
  - `.gaslighting/MEMORY.md`
33
+ - `.gaslighting/AGENT_RUNTIME.md`
33
34
  - `.gaslighting/STACK_POLICY.md`
34
35
  - `.gaslighting/PROJECT_CARE.md`
35
36
  - `AGENTS.md`
@@ -40,9 +41,11 @@ It generates:
40
41
  ## Quick Start
41
42
 
42
43
  ```bash
43
- npx gaslighting-engine "I want to build a hospital website."
44
+ npx gaslighting-engine@latest start "I want to build a hospital website."
44
45
  ```
45
46
 
47
+ This generates the discipline documents, opens the local Mission Control GUI, and prepares a Codex launch command.
48
+
46
49
  Short aliases after global install or local link:
47
50
 
48
51
  ```bash
@@ -84,6 +87,9 @@ gaslighting init "I want to build a hospital website."
84
87
  gaslighting-engine generate "Build an ecommerce MVP."
85
88
  gaslighting-engine update "The hospital is actually an OB-GYN clinic, not dermatology."
86
89
  gaslighting-engine doctor
90
+ gaslighting-engine start "Build a hospital homepage"
91
+ gaslighting-engine cockpit
92
+ gaslighting-engine run "Build a hospital homepage"
87
93
  gaslighting-engine upgrade
88
94
  gaslighting-engine codex-install
89
95
  gaslighting-engine codex-doctor
@@ -99,6 +105,49 @@ Gaslighting-engine keeps project-control documents in `.gaslighting/` by default
99
105
 
100
106
  Only root `AGENTS.md` is written at the top level because Codex expects project guidance there. It is a thin pointer into `.gaslighting/`.
101
107
 
108
+ ## Mission Control
109
+
110
+ Mission Control is the Codex-first GUI agent surface:
111
+
112
+ ```bash
113
+ npx gaslighting-engine@latest start "I want to build a hospital website."
114
+ ```
115
+
116
+ It shows:
117
+
118
+ - project purpose
119
+ - generated control documents
120
+ - missing information
121
+ - Git/GitHub and deployment care checks
122
+ - Codex runtime args
123
+ - Start Codex action
124
+
125
+ By default, `Start Codex` closes the local cockpit server and continues Codex in the same terminal that launched Mission Control. This keeps Cursor, Warp, Windows Terminal, or any other terminal flow intact.
126
+
127
+ If you explicitly want a separate terminal, add:
128
+
129
+ ```bash
130
+ npx gaslighting-engine@latest start "I want to build a hospital website." --new-terminal
131
+ ```
132
+
133
+ To customize Codex flags, use:
134
+
135
+ ```bash
136
+ npx gaslighting-engine@latest start "I want to build a hospital website." --codex-args="--search --dangerously-bypass-approvals-and-sandbox"
137
+ ```
138
+
139
+ Open it for an existing project:
140
+
141
+ ```bash
142
+ npx gaslighting-engine@latest cockpit
143
+ ```
144
+
145
+ Run without the GUI:
146
+
147
+ ```bash
148
+ npx gaslighting-engine@latest run "I want to build a hospital website."
149
+ ```
150
+
102
151
  ## Project Care
103
152
 
104
153
  Gaslighting-engine also writes `.gaslighting/PROJECT_CARE.md`.
package/dist/cli.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { Command, InvalidArgumentError } from "commander";
2
2
  import { runAgents } from "./commands/agents.js";
3
3
  import { runCare } from "./commands/care.js";
4
+ import { runCockpit, runCodexRun, runStart } from "./commands/cockpit.js";
4
5
  import { runCodexDoctor, runCodexInstall } from "./commands/codexInstall.js";
5
6
  import { runDoctor } from "./commands/doctor.js";
6
7
  import { runGenerate } from "./commands/generate.js";
@@ -60,6 +61,7 @@ Examples:
60
61
  $ gaslighting-engine init "Build a landing page" --dry-run
61
62
  $ gaslighting-engine doctor
62
63
  $ gaslighting-engine care --github-url https://github.com/user/repo.git
64
+ $ gaslighting-engine start "Build a hospital homepage"
63
65
  $ gaslighting-engine upgrade
64
66
  $ gaslighting-engine codex-install --force
65
67
 
@@ -104,6 +106,44 @@ Defaults:
104
106
  .option("--github-url <url>", "initialize git if needed and connect origin to this GitHub URL")
105
107
  .option("--force-remote", "replace an existing origin remote when used with --github-url")
106
108
  .action(runCare);
109
+ program
110
+ .command("start <request>")
111
+ .description("Generate discipline docs, open Mission Control, and prepare Codex launch")
112
+ .option("--force", "overwrite existing files")
113
+ .option("--no-force", "preserve existing files and create .new variants instead")
114
+ .option("--no-open", "print the Mission Control URL without opening a browser")
115
+ .option("--port <port>", "Mission Control port, random by default")
116
+ .option("--path <path>", "target directory")
117
+ .option("--lang <lang>", "language: en or ko", "en")
118
+ .option("--type <type>", "project type override", parseProjectType)
119
+ .option("--codex-args <args>", "Codex CLI args used by the Start Codex button")
120
+ .option("--new-terminal", "launch Codex in a separate terminal instead of handing off the current terminal")
121
+ .action(runStart);
122
+ program
123
+ .command("cockpit [request]")
124
+ .description("Open the local Gaslighting Mission Control GUI")
125
+ .option("--no-generate", "open without regenerating discipline docs")
126
+ .option("--force", "overwrite existing files")
127
+ .option("--no-force", "preserve existing files and create .new variants instead")
128
+ .option("--no-open", "print the Mission Control URL without opening a browser")
129
+ .option("--port <port>", "Mission Control port, random by default")
130
+ .option("--path <path>", "target directory")
131
+ .option("--lang <lang>", "language: en or ko", "en")
132
+ .option("--type <type>", "project type override", parseProjectType)
133
+ .option("--codex-args <args>", "Codex CLI args used by the Start Codex button")
134
+ .option("--new-terminal", "launch Codex in a separate terminal instead of handing off the current terminal")
135
+ .action(runCockpit);
136
+ program
137
+ .command("run <request>")
138
+ .description("Generate discipline docs and launch Codex without opening the GUI")
139
+ .option("--force", "overwrite existing files")
140
+ .option("--no-force", "preserve existing files and create .new variants instead")
141
+ .option("--path <path>", "target directory")
142
+ .option("--lang <lang>", "language: en or ko", "en")
143
+ .option("--type <type>", "project type override", parseProjectType)
144
+ .option("--codex-args <args>", "Codex CLI args")
145
+ .option("--new-terminal", "launch Codex in a separate terminal instead of handing off the current terminal")
146
+ .action(runCodexRun);
107
147
  program
108
148
  .command("upgrade")
109
149
  .description("Update the local Codex Gaslighting install with one simple command")
@@ -134,7 +174,7 @@ export function normalizeDefaultInitArgv(argv) {
134
174
  return argv;
135
175
  if (args.some((arg) => ["--help", "-h", "--version", "-V"].includes(arg)))
136
176
  return argv;
137
- const commands = new Set(["init", "generate", "update", "doctor", "skill", "agents", "care", "upgrade", "codex-install", "codex-doctor"]);
177
+ const commands = new Set(["init", "generate", "update", "doctor", "skill", "agents", "care", "start", "cockpit", "run", "upgrade", "codex-install", "codex-doctor"]);
138
178
  const firstMeaningful = args.find((arg) => !arg.startsWith("-"));
139
179
  if (firstMeaningful && commands.has(firstMeaningful))
140
180
  return argv;
@@ -0,0 +1,197 @@
1
+ import { execFileSync, spawn } from "node:child_process";
2
+ import { existsSync, readFileSync } from "node:fs";
3
+ import { createServer } from "node:http";
4
+ import { join, resolve } from "node:path";
5
+ import { platform } from "node:os";
6
+ import { analyze } from "../core/analyze.js";
7
+ import { buildCodexCommand, getDefaultCodexArgs, launchCodexInCurrentTerminal, launchCodexInNewTerminal } from "../core/codexRuntime.js";
8
+ import { pageList, projectPurpose } from "../core/content.js";
9
+ import { generateCodexInstallDocs, generateDocs, generateProjectCareDoc } from "../core/generateDocs.js";
10
+ import { renderCockpitHtml } from "../core/cockpitHtml.js";
11
+ import { connectGitHubRemote, inspectProjectCare } from "../core/projectCare.js";
12
+ import { printBanner } from "../utils/banner.js";
13
+ import { writeDocs } from "../utils/file.js";
14
+ import { packageVersion } from "../version.js";
15
+ const docFiles = [
16
+ "AGENTS.md",
17
+ ".gaslighting/GASLIGHTING.md",
18
+ ".gaslighting/PRD.md",
19
+ ".gaslighting/MISSING_INFO.md",
20
+ ".gaslighting/ASSUMPTIONS.md",
21
+ ".gaslighting/DECISION_LOG.md",
22
+ ".gaslighting/MEMORY.md",
23
+ ".gaslighting/AGENT_RUNTIME.md",
24
+ ".gaslighting/PROJECT_CARE.md",
25
+ ];
26
+ export async function runCockpit(rawUserRequest = "", options) {
27
+ await openCockpit(rawUserRequest, { ...options, generate: Boolean(rawUserRequest) && options.generate !== false, force: options.force ?? true });
28
+ }
29
+ export async function runStart(rawUserRequest, options) {
30
+ await openCockpit(rawUserRequest, { ...options, generate: true, force: options.force ?? true });
31
+ }
32
+ export function runCodexRun(rawUserRequest, options) {
33
+ const root = resolve(options.path ?? process.cwd());
34
+ const input = makeInput(rawUserRequest, options);
35
+ writeDocs(root, generateDocs(input, root).docs, options.force ?? true, false);
36
+ writeDocs(root, generateCodexInstallDocs(), true, false);
37
+ printBanner("CODEX RUN");
38
+ console.log(options.newTerminal ? "Starting Codex in a new terminal." : "Handing this terminal over to Codex.");
39
+ console.log(buildCodexCommand({ root, request: rawUserRequest, codexArgs: options.codexArgs }));
40
+ const result = options.newTerminal
41
+ ? launchCodexInNewTerminal({ root, request: rawUserRequest, codexArgs: options.codexArgs })
42
+ : launchCodexInCurrentTerminal({ root, request: rawUserRequest, codexArgs: options.codexArgs });
43
+ if (!result.ok)
44
+ console.log(result.message);
45
+ }
46
+ async function openCockpit(rawUserRequest, options) {
47
+ const root = resolve(options.path ?? process.cwd());
48
+ let currentRequest = rawUserRequest || readExistingRequest(root) || "Build a practical MVP without shrinking scope.";
49
+ let currentInput = makeInput(currentRequest, options);
50
+ if (options.generate) {
51
+ writeDocs(root, generateDocs(currentInput, root).docs, options.force ?? true, false);
52
+ writeDocs(root, generateCodexInstallDocs(), true, false);
53
+ }
54
+ const requestedPort = Number.parseInt(options.port ?? "0", 10);
55
+ const server = createServer(async (request, response) => {
56
+ try {
57
+ if (request.method === "GET" && request.url === "/")
58
+ return sendHtml(response, renderCockpitHtml());
59
+ if (request.method === "GET" && request.url === "/api/state")
60
+ return sendJson(response, buildState(root, currentRequest, currentInput, options));
61
+ if (request.method === "POST" && request.url === "/api/generate") {
62
+ const body = await readJson(request);
63
+ currentRequest = String(body.request || currentRequest);
64
+ currentInput = makeInput(currentRequest, options);
65
+ writeDocs(root, generateDocs(currentInput, root).docs, true, false);
66
+ return sendJson(response, { state: buildState(root, currentRequest, currentInput, options) });
67
+ }
68
+ if (request.method === "POST" && request.url === "/api/start-codex") {
69
+ const body = await readJson(request);
70
+ currentRequest = String(body.request || currentRequest);
71
+ currentInput = makeInput(currentRequest, options);
72
+ writeDocs(root, generateDocs(currentInput, root).docs, true, false);
73
+ if (options.newTerminal) {
74
+ const result = launchCodexInNewTerminal({ root, request: currentRequest, codexArgs: options.codexArgs });
75
+ return sendJson(response, { ...result });
76
+ }
77
+ const commandPreview = launchMessageForCurrentTerminal(root, currentRequest, options);
78
+ sendJson(response, commandPreview);
79
+ setTimeout(() => {
80
+ server.close(() => {
81
+ console.log("\nMission Control is handing off to Codex in this terminal.\n");
82
+ launchCodexInCurrentTerminal({ root, request: currentRequest, codexArgs: options.codexArgs });
83
+ });
84
+ }, 250);
85
+ return;
86
+ }
87
+ if (request.method === "POST" && request.url === "/api/connect-github") {
88
+ const body = await readJson(request);
89
+ const githubUrl = String(body.githubUrl || "");
90
+ const results = connectGitHubRemote(root, githubUrl, Boolean(body.forceRemote));
91
+ writeDocs(root, generateProjectCareDoc(currentInput, root), true, false);
92
+ return sendJson(response, { results, state: buildState(root, currentRequest, currentInput, options) });
93
+ }
94
+ sendJson(response, { message: "Not found." }, 404);
95
+ }
96
+ catch (error) {
97
+ const message = error instanceof Error ? error.message : "Unknown cockpit error.";
98
+ sendJson(response, { message }, 500);
99
+ }
100
+ });
101
+ await new Promise((resolveListen) => server.listen(requestedPort, "127.0.0.1", resolveListen));
102
+ const address = server.address();
103
+ const port = typeof address === "object" && address ? address.port : requestedPort;
104
+ const url = `http://127.0.0.1:${port}`;
105
+ printBanner("MISSION CONTROL");
106
+ console.log(`Project: ${currentRequest}`);
107
+ console.log(`Root: ${root}`);
108
+ console.log(`URL: ${url}`);
109
+ console.log("Close this terminal session to stop the cockpit.");
110
+ if (options.open !== false)
111
+ openBrowser(url);
112
+ }
113
+ function buildState(root, request, input, options) {
114
+ const analysis = analyze(input);
115
+ const docsPresent = Object.fromEntries(docFiles.map((file) => [file, existsSync(join(root, file))]));
116
+ return {
117
+ version: packageVersion,
118
+ root,
119
+ request,
120
+ analysis,
121
+ purpose: projectPurpose(analysis.projectType),
122
+ pages: pageList(analysis.projectType),
123
+ care: inspectProjectCare(root),
124
+ docsPresent,
125
+ codexArgs: options.codexArgs || getDefaultCodexArgs(),
126
+ launchMode: options.newTerminal ? "new_terminal" : "current_terminal",
127
+ codexAvailable: isCommandAvailable("codex"),
128
+ };
129
+ }
130
+ function launchMessageForCurrentTerminal(root, request, options) {
131
+ return {
132
+ ok: true,
133
+ command: buildCodexCommand({ root, request, codexArgs: options.codexArgs }),
134
+ message: "Mission Control will close and Codex will continue in the same terminal that launched it.",
135
+ };
136
+ }
137
+ function makeInput(rawUserRequest, options) {
138
+ return {
139
+ rawUserRequest,
140
+ projectType: options.type,
141
+ language: options.lang ?? "en",
142
+ mode: "hardcore",
143
+ fullScope: true,
144
+ noTodo: true,
145
+ noShortcut: true,
146
+ force: options.force ?? true,
147
+ };
148
+ }
149
+ function readExistingRequest(root) {
150
+ const carePath = join(root, ".gaslighting", "PROJECT_CARE.md");
151
+ if (!existsSync(carePath))
152
+ return undefined;
153
+ const content = readFileSync(carePath, "utf8");
154
+ return /^- Original request: (.+)$/m.exec(content)?.[1];
155
+ }
156
+ function isCommandAvailable(command) {
157
+ try {
158
+ if (platform() === "win32")
159
+ execFileSync("where", [command], { stdio: "ignore" });
160
+ else
161
+ execFileSync("sh", ["-lc", `command -v ${command}`], { stdio: "ignore" });
162
+ return true;
163
+ }
164
+ catch {
165
+ return false;
166
+ }
167
+ }
168
+ function openBrowser(url) {
169
+ const command = platform() === "win32"
170
+ ? { file: "cmd.exe", args: ["/c", "start", "", url] }
171
+ : platform() === "darwin"
172
+ ? { file: "open", args: [url] }
173
+ : { file: "xdg-open", args: [url] };
174
+ try {
175
+ const child = spawn(command.file, command.args, { detached: true, stdio: "ignore" });
176
+ child.unref();
177
+ }
178
+ catch {
179
+ // The printed URL is the fallback.
180
+ }
181
+ }
182
+ async function readJson(request) {
183
+ const chunks = [];
184
+ for await (const chunk of request)
185
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
186
+ if (chunks.length === 0)
187
+ return {};
188
+ return JSON.parse(Buffer.concat(chunks).toString("utf8"));
189
+ }
190
+ function sendHtml(response, html) {
191
+ response.writeHead(200, { "content-type": "text/html; charset=utf-8" });
192
+ response.end(html);
193
+ }
194
+ function sendJson(response, data, status = 200) {
195
+ response.writeHead(status, { "content-type": "application/json; charset=utf-8" });
196
+ response.end(JSON.stringify(data));
197
+ }
@@ -18,6 +18,7 @@ export function runDoctor(options) {
18
18
  fileCheck(root, ".gaslighting/MISSING_INFO.md"),
19
19
  fileCheck(root, ".gaslighting/DECISION_LOG.md"),
20
20
  fileCheck(root, ".gaslighting/MEMORY.md"),
21
+ fileCheck(root, ".gaslighting/AGENT_RUNTIME.md"),
21
22
  fileCheck(root, ".gaslighting/PROJECT_CARE.md"),
22
23
  { name: ".gaslighting/GASLIGHTING.md contains project purpose", ok: /Project Purpose/i.test(gaslighting) },
23
24
  { name: ".gaslighting/GASLIGHTING.md contains no-fake-completion rules", ok: /No Fake Completion|Fake completion/i.test(gaslighting) },