@pmelab/gtd 6.8.0 → 6.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -7
- package/dist/gtd.bundle.mjs +140 -35
- package/docs/configuration.md +27 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -58,13 +58,19 @@ gtd init
|
|
|
58
58
|
This writes a `.gtdrc.json` for the bundled unified workflow, with each agent
|
|
59
59
|
state's prompt saved as an editable Markdown file under `gtd-prompts/` and
|
|
60
60
|
referenced from the config (review and commit both). Edit a prompt by editing
|
|
61
|
-
its `gtd-prompts/*.md` file — no config edit needed.
|
|
62
|
-
`
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
61
|
+
its `gtd-prompts/*.md` file — no config edit needed. File references resolve
|
|
62
|
+
relative to **the `.gtdrc` that declares them**, so a config and its
|
|
63
|
+
`gtd-prompts/` can even live in a parent directory shared across repos while gtd
|
|
64
|
+
runs from each repo root — run `gtd init` from that parent directory (it need
|
|
65
|
+
not be a git repo) to scaffold it there (see
|
|
66
|
+
[Configuration](docs/configuration.md#content-values-inline-or-a-file-reference)).
|
|
67
|
+
It also seeds a top-level `modes:` block suggesting **Prettier** as the
|
|
68
|
+
steering-file formatter (`npx prettier --write` for the built-in `qa`/`review`
|
|
69
|
+
modes — format only, so gtd still validates); edit or drop it freely (swap in
|
|
70
|
+
dprint, a script, or delete the key). `gtd init` takes no argument. gtd ships
|
|
71
|
+
**no** default workflow: a state command run before `gtd init` fails, pointing
|
|
72
|
+
you back here. See [Configuration](docs/configuration.md#gtd-init) for the
|
|
73
|
+
template.
|
|
68
74
|
|
|
69
75
|
## How it works
|
|
70
76
|
|
package/dist/gtd.bundle.mjs
CHANGED
|
@@ -189815,6 +189815,49 @@ const resolveContent = (value, configDir, where, errors) => {
|
|
|
189815
189815
|
return;
|
|
189816
189816
|
}
|
|
189817
189817
|
};
|
|
189818
|
+
/**
|
|
189819
|
+
* Inline every `./`/`../` content file reference in ONE raw `workflow:` value
|
|
189820
|
+
* against `configDir` — the directory of the `.gtdrc` that DECLARED it — and
|
|
189821
|
+
* return a copy with those references replaced by the referenced file's text.
|
|
189822
|
+
*
|
|
189823
|
+
* Used by `src/Config.ts`'s `loadMerged` to resolve each config level's
|
|
189824
|
+
* references against its OWN file's directory BEFORE the levels are
|
|
189825
|
+
* deep-merged. The merge collapses every level into one anonymous object, so it
|
|
189826
|
+
* erases which file a given `states.x.prompt` came from; resolving up front,
|
|
189827
|
+
* per level, is the only way `./gtd-prompts/x.md` in a parent `.gtdrc` resolves
|
|
189828
|
+
* against the parent (not the cwd a child repo runs from). `compileWorkflowConfig`
|
|
189829
|
+
* is then invoked with `inlineFileRefs: false` on the merged result.
|
|
189830
|
+
*
|
|
189831
|
+
* Malformed shapes (a non-object workflow, non-object `states`, a non-object
|
|
189832
|
+
* state, a non-string content value) pass through untouched —
|
|
189833
|
+
* `compileWorkflowConfig`/`validateDefinition` own those findings; this pass
|
|
189834
|
+
* only rewrites the strings it recognizes as file references, collecting a load
|
|
189835
|
+
* error (via `resolveContent`) for any that is missing or unreadable.
|
|
189836
|
+
*/
|
|
189837
|
+
const inlineWorkflowFileRefs = (rawWorkflow, configDir, errors) => {
|
|
189838
|
+
if (!isPlainObject$1(rawWorkflow)) return rawWorkflow;
|
|
189839
|
+
const rawStates = rawWorkflow["states"];
|
|
189840
|
+
if (!isPlainObject$1(rawStates)) return rawWorkflow;
|
|
189841
|
+
const states = {};
|
|
189842
|
+
for (const [name, state] of Object.entries(rawStates)) {
|
|
189843
|
+
if (!isPlainObject$1(state)) {
|
|
189844
|
+
states[name] = state;
|
|
189845
|
+
continue;
|
|
189846
|
+
}
|
|
189847
|
+
const next = { ...state };
|
|
189848
|
+
for (const key of CONTENT_KEYS) {
|
|
189849
|
+
const value = state[key];
|
|
189850
|
+
if (typeof value !== "string" || !isFileReference(value)) continue;
|
|
189851
|
+
const resolved = resolveContent(value, configDir, `state "${name}" (${key})`, errors);
|
|
189852
|
+
if (resolved !== void 0) next[key] = resolved;
|
|
189853
|
+
}
|
|
189854
|
+
states[name] = next;
|
|
189855
|
+
}
|
|
189856
|
+
return {
|
|
189857
|
+
...rawWorkflow,
|
|
189858
|
+
states
|
|
189859
|
+
};
|
|
189860
|
+
};
|
|
189818
189861
|
const KNOWN_EDGE_KEYS = /* @__PURE__ */ new Set(["to", "describe"]);
|
|
189819
189862
|
/**
|
|
189820
189863
|
* Compile one `on` row's value into an `OnEdge` (or `undefined`, pushing a
|
|
@@ -189883,8 +189926,15 @@ const compileRetry = (raw, name, errors) => {
|
|
|
189883
189926
|
otherwise
|
|
189884
189927
|
} : void 0;
|
|
189885
189928
|
};
|
|
189886
|
-
/**
|
|
189887
|
-
|
|
189929
|
+
/**
|
|
189930
|
+
* Exactly one of script/prompt/message/commit, each a string, file-refs
|
|
189931
|
+
* auto-inlined. `inlineFileRefs` is `false` only when the caller
|
|
189932
|
+
* (`src/Config.ts`'s `loadMerged`) has ALREADY inlined every reference per
|
|
189933
|
+
* declaring file — the content is then taken verbatim, so a `script:` whose
|
|
189934
|
+
* inlined text happens to begin with `./` is never mistaken for a second file
|
|
189935
|
+
* reference and re-resolved.
|
|
189936
|
+
*/
|
|
189937
|
+
const compileContent = (raw, name, configDir, errors, inlineFileRefs) => {
|
|
189888
189938
|
const content = {};
|
|
189889
189939
|
for (const key of CONTENT_KEYS) {
|
|
189890
189940
|
const rawValue = raw[key];
|
|
@@ -189893,6 +189943,10 @@ const compileContent = (raw, name, configDir, errors) => {
|
|
|
189893
189943
|
errors.push(`state "${name}": "${key}" must be a string`);
|
|
189894
189944
|
continue;
|
|
189895
189945
|
}
|
|
189946
|
+
if (!inlineFileRefs) {
|
|
189947
|
+
content[key] = rawValue;
|
|
189948
|
+
continue;
|
|
189949
|
+
}
|
|
189896
189950
|
const resolved = resolveContent(rawValue, configDir, `state "${name}" (${key})`, errors);
|
|
189897
189951
|
if (resolved !== void 0) content[key] = resolved;
|
|
189898
189952
|
}
|
|
@@ -189991,7 +190045,7 @@ const assembleStateDef = (parts) => ({
|
|
|
189991
190045
|
...assembleContentFields(parts.content)
|
|
189992
190046
|
});
|
|
189993
190047
|
/** One state's full shape: actor, content, `on`, `initial`, `retry`. */
|
|
189994
|
-
const compileState = (name, raw, configDir, errors) => {
|
|
190048
|
+
const compileState = (name, raw, configDir, errors, inlineFileRefs) => {
|
|
189995
190049
|
if (!isPlainObject$1(raw)) {
|
|
189996
190050
|
errors.push(`state "${name}": must be an object, got ${describeType(raw)}`);
|
|
189997
190051
|
return {};
|
|
@@ -190000,7 +190054,7 @@ const compileState = (name, raw, configDir, errors) => {
|
|
|
190000
190054
|
if (unknownKeys.length > 0) errors.push(`state "${name}": unknown key(s) ${unknownKeys.join(", ")}`);
|
|
190001
190055
|
return assembleStateDef({
|
|
190002
190056
|
actor: compileActor(raw, name, errors),
|
|
190003
|
-
content: compileContent(raw, name, configDir, errors),
|
|
190057
|
+
content: compileContent(raw, name, configDir, errors, inlineFileRefs),
|
|
190004
190058
|
on: compileOn(raw.on, name, errors),
|
|
190005
190059
|
initial: compileInitial(raw, name, errors),
|
|
190006
190060
|
retry: compileRetry(raw.retry, name, errors),
|
|
@@ -190019,12 +190073,15 @@ const compileState = (name, raw, configDir, errors) => {
|
|
|
190019
190073
|
* file's own directory, used to resolve `./`/`../` file references. `rcModes`
|
|
190020
190074
|
* is the already-compiled top-level `.gtdrc` `modes:` key (`src/Config.ts`),
|
|
190021
190075
|
* layered over the workflow's own `modes:` per half BEFORE validation — so a
|
|
190022
|
-
* state may name a mode either layer declares.
|
|
190023
|
-
* `
|
|
190024
|
-
*
|
|
190025
|
-
*
|
|
190026
|
-
|
|
190027
|
-
|
|
190076
|
+
* state may name a mode either layer declares. `inlineFileRefs` (default
|
|
190077
|
+
* `true`) inlines content file references against `configDir`; the real config
|
|
190078
|
+
* path (`src/Config.ts`'s `loadMerged`) passes `false` because it has already
|
|
190079
|
+
* inlined every reference per declaring file across the merge chain, so
|
|
190080
|
+
* `configDir` is then irrelevant. Throws a single `Error` (message:
|
|
190081
|
+
* `"workflow config:\n - ..."`, one line per finding) on ANY config-shape
|
|
190082
|
+
* problem or `validateDefinition` finding — never partially succeeds.
|
|
190083
|
+
*/
|
|
190084
|
+
const compileWorkflowConfig = (raw, configDir, rcModes, inlineFileRefs = true) => {
|
|
190028
190085
|
if (!isPlainObject$1(raw)) throw new Error(`workflow config: must be an object, got ${describeType(raw)}`);
|
|
190029
190086
|
const errors = [];
|
|
190030
190087
|
const unknownTopKeys = Object.keys(raw).filter((k) => !KNOWN_TOP_KEYS.has(k));
|
|
@@ -190037,7 +190094,7 @@ const compileWorkflowConfig = (raw, configDir, rcModes) => {
|
|
|
190037
190094
|
throw new Error(formatErrors(errors));
|
|
190038
190095
|
}
|
|
190039
190096
|
const states = {};
|
|
190040
|
-
for (const [name, s] of Object.entries(rawStates)) states[name] = compileState(name, s, configDir, errors);
|
|
190097
|
+
for (const [name, s] of Object.entries(rawStates)) states[name] = compileState(name, s, configDir, errors, inlineFileRefs);
|
|
190041
190098
|
const definition = modes !== void 0 ? {
|
|
190042
190099
|
states,
|
|
190043
190100
|
modes
|
|
@@ -190327,22 +190384,50 @@ const makeExplorer = () => (0, import_dist.cosmiconfig)("gtd", {
|
|
|
190327
190384
|
/**
|
|
190328
190385
|
* Load and deep-merge every config level from cwd up the directory chain.
|
|
190329
190386
|
* Innermost (cwd) wins. Returns the merged plain object (undecoded).
|
|
190330
|
-
|
|
190387
|
+
*
|
|
190388
|
+
* A `workflow:` value's `./`/`../` content file references are inlined PER
|
|
190389
|
+
* LEVEL against that level's OWN file directory (`dirname(result.filepath)`)
|
|
190390
|
+
* BEFORE merging — because the deep-merge collapses every level into one
|
|
190391
|
+
* anonymous object and erases which file each `states.x.prompt` came from.
|
|
190392
|
+
* Resolving up front is what lets a `.gtdrc` stored in a parent directory
|
|
190393
|
+
* reference `./gtd-prompts/x.md` and have it resolve against the parent, even
|
|
190394
|
+
* though gtd runs from a child repo (a different cwd). Any missing/unreadable
|
|
190395
|
+
* reference is collected and thrown as one aggregated `workflow config:` error,
|
|
190396
|
+
* exactly like the compiler's own resolution. The merged, already-inlined
|
|
190397
|
+
* `workflow:` is later compiled with `inlineFileRefs: false` (see
|
|
190398
|
+
* `toOperations`).
|
|
190399
|
+
*/
|
|
190400
|
+
/**
|
|
190401
|
+
* Normalize one loaded config level: validate it is a plain object, then inline
|
|
190402
|
+
* its `workflow:` file references against its OWN file's directory (collecting
|
|
190403
|
+
* any missing/unreadable reference into `refErrors`). A level with no
|
|
190404
|
+
* `workflow:` key passes through untouched.
|
|
190405
|
+
*/
|
|
190406
|
+
const inlineLevel = (config, filepath, refErrors) => {
|
|
190407
|
+
if (!isPlainObject(config)) throw new Error(`${filepath}: config must be a plain object, got ${Array.isArray(config) ? "array" : String(config)}`);
|
|
190408
|
+
if (config["workflow"] === void 0) return config;
|
|
190409
|
+
return {
|
|
190410
|
+
...config,
|
|
190411
|
+
workflow: inlineWorkflowFileRefs(config["workflow"], dirname(filepath), refErrors)
|
|
190412
|
+
};
|
|
190413
|
+
};
|
|
190414
|
+
/** Read every config level from cwd up the chain, outermost→innermost, each with its file references already inlined per declaring file. Throws on a malformed level or an aggregated set of bad references. */
|
|
190415
|
+
const readConfigLevels = async (root) => {
|
|
190416
|
+
const chain = walkUp(root, homedir());
|
|
190417
|
+
const explorer = makeExplorer();
|
|
190418
|
+
const levels = [];
|
|
190419
|
+
const refErrors = [];
|
|
190420
|
+
for (let i = chain.length - 1; i >= 0; i--) {
|
|
190421
|
+
const result = await explorer.search(chain[i]);
|
|
190422
|
+
if (!result || result.isEmpty) continue;
|
|
190423
|
+
levels.push(inlineLevel(result.config, result.filepath, refErrors));
|
|
190424
|
+
}
|
|
190425
|
+
if (refErrors.length > 0) throw new Error(`workflow config:\n${refErrors.map((e) => ` - ${e}`).join("\n")}`);
|
|
190426
|
+
return levels;
|
|
190427
|
+
};
|
|
190331
190428
|
const loadMerged = (root) => tryPromise({
|
|
190332
190429
|
try: async () => {
|
|
190333
|
-
|
|
190334
|
-
const chain = walkUp(root, home);
|
|
190335
|
-
const explorer = makeExplorer();
|
|
190336
|
-
const levels = [];
|
|
190337
|
-
for (let i = chain.length - 1; i >= 0; i--) {
|
|
190338
|
-
const dir = chain[i];
|
|
190339
|
-
const result = await explorer.search(dir);
|
|
190340
|
-
if (result && !result.isEmpty) {
|
|
190341
|
-
if (!isPlainObject(result.config)) throw new Error(`${result.filepath}: config must be a plain object, got ${Array.isArray(result.config) ? "array" : String(result.config)}`);
|
|
190342
|
-
levels.push(result.config);
|
|
190343
|
-
}
|
|
190344
|
-
}
|
|
190345
|
-
return levels.reduce((acc, level) => deepMerge(acc, level), {});
|
|
190430
|
+
return (await readConfigLevels(root)).reduce((acc, level) => deepMerge(acc, level), {});
|
|
190346
190431
|
},
|
|
190347
190432
|
catch: (e) => e instanceof Error ? e : new Error(String(e))
|
|
190348
190433
|
});
|
|
@@ -190396,18 +190481,19 @@ const compileRcVars = (raw) => {
|
|
|
190396
190481
|
const NO_WORKFLOW_MESSAGE = "gtd: no workflow configured — run `gtd init` to create .gtdrc.json";
|
|
190397
190482
|
/**
|
|
190398
190483
|
* Compile the decoded config's `workflow:` key plus its top-level
|
|
190399
|
-
* `vars:`/`modes:` keys into `ConfigOperations`.
|
|
190400
|
-
*
|
|
190401
|
-
*
|
|
190402
|
-
*
|
|
190403
|
-
*
|
|
190404
|
-
* invalid
|
|
190484
|
+
* `vars:`/`modes:` keys into `ConfigOperations`. Its `./`/`../` content file
|
|
190485
|
+
* references were already inlined per declaring file by `loadMerged`, so the
|
|
190486
|
+
* compiler is invoked with `inlineFileRefs: false` and `root` is passed only as
|
|
190487
|
+
* an (unused) `configDir` placeholder. Throws `NO_WORKFLOW_MESSAGE` when no
|
|
190488
|
+
* `workflow:` key is present (there is no bundled default to fall back to — see
|
|
190489
|
+
* `gtd init`), or (via `compileWorkflowConfig`/`compileRcVars`) on any invalid
|
|
190490
|
+
* workflow/vars.
|
|
190405
190491
|
*/
|
|
190406
190492
|
const toOperations = (decoded, root) => {
|
|
190407
190493
|
const rcVars = compileRcVars(decoded.vars);
|
|
190408
190494
|
const rcModes = compileRcModes(decoded.modes);
|
|
190409
190495
|
if (decoded.workflow === void 0) throw new Error(NO_WORKFLOW_MESSAGE);
|
|
190410
|
-
const { definition, vars: workflowVars } = compileWorkflowConfig(decoded.workflow, root, rcModes);
|
|
190496
|
+
const { definition, vars: workflowVars } = compileWorkflowConfig(decoded.workflow, root, rcModes, false);
|
|
190411
190497
|
return {
|
|
190412
190498
|
workflow: definition,
|
|
190413
190499
|
workflowVars,
|
|
@@ -204404,7 +204490,7 @@ const runInitCommand = (argv, json, write) => gen(function* () {
|
|
|
204404
204490
|
if (args.length > 0) return yield* fail$2(/* @__PURE__ */ new Error(`gtd init: too many arguments — init takes no argument, got: ${args.join(", ")}`));
|
|
204405
204491
|
const git = yield* GitService;
|
|
204406
204492
|
const fs = yield* FileSystem;
|
|
204407
|
-
yield*
|
|
204493
|
+
const inRepo = yield* assertInitLocation(git, fs);
|
|
204408
204494
|
const { root } = yield* Cwd;
|
|
204409
204495
|
if (yield* configPresentAt(root)) return yield* fail$2(/* @__PURE__ */ new Error("gtd init: a gtd config already exists — remove it before re-initializing"));
|
|
204410
204496
|
const toError = (e) => e instanceof Error ? e : new Error(String(e));
|
|
@@ -204418,11 +204504,12 @@ const runInitCommand = (argv, json, write) => gen(function* () {
|
|
|
204418
204504
|
if (json) write(JSON.stringify({
|
|
204419
204505
|
written: ".gtdrc.json",
|
|
204420
204506
|
workflow: "unified",
|
|
204421
|
-
prompts: scaffold.prompts.map((p) => p.path)
|
|
204507
|
+
prompts: scaffold.prompts.map((p) => p.path),
|
|
204508
|
+
inRepo
|
|
204422
204509
|
}) + "\n");
|
|
204423
204510
|
else {
|
|
204424
204511
|
const promptCount = scaffold.prompts.length;
|
|
204425
|
-
write(`Wrote .gtdrc.json with the bundled unified workflow, and its ${promptCount} agent prompt${promptCount === 1 ? "" : "s"} as editable Markdown under ${PROMPTS_DIR}/ (referenced from the config).\n\
|
|
204512
|
+
write(`Wrote .gtdrc.json with the bundled unified workflow, and its ${promptCount} agent prompt${promptCount === 1 ? "" : "s"} as editable Markdown under ${PROMPTS_DIR}/ (referenced from the config).\n\n` + (inRepo ? "Review and commit them before starting: an uncommitted .gtdrc.json (or prompt\nfile) counts as a pending change, so the initial state would capture it on the\nfirst step. Once committed, run `gtd step human` to begin.\n" : "This directory is not a git repository, so there is nothing to commit here. The\nconfig applies to any gtd repository nested below it — gtd discovers it by\nwalking up from the repository root. Run `gtd step human` from such a repo to\nbegin.\n"));
|
|
204426
204513
|
}
|
|
204427
204514
|
});
|
|
204428
204515
|
/**
|
|
@@ -204812,6 +204899,24 @@ const assertRunningFromRepoRoot = (git, fs) => gen(function* () {
|
|
|
204812
204899
|
const topLevel = yield* git.topLevel();
|
|
204813
204900
|
if ((yield* fs.realPath(topLevel)) !== (yield* fs.realPath(process.cwd()))) return yield* fail$2(/* @__PURE__ */ new Error(`gtd must be run from the repository root (${topLevel}); the current directory is ${process.cwd()}`));
|
|
204814
204901
|
});
|
|
204902
|
+
/**
|
|
204903
|
+
* `gtd init` writes only a `.gtdrc.json` (+ prompt files) — it derives no state,
|
|
204904
|
+
* so unlike the state commands it need not sit in a git repository at all. It
|
|
204905
|
+
* may run EITHER at a repository root OR in a directory outside any repository —
|
|
204906
|
+
* the latter scaffolds a shared config a nested repo picks up by walking up the
|
|
204907
|
+
* cwd→home chain (see the parent-dir layout in docs/configuration.md). The one
|
|
204908
|
+
* placement it must refuse is a repository SUBDIRECTORY: gtd runs from the repo
|
|
204909
|
+
* root and discovers config by walking UP, so a config written below the root
|
|
204910
|
+
* would silently never be found. Returns whether cwd is inside a repository (at
|
|
204911
|
+
* its root), so the caller can tailor the "commit before starting" guidance —
|
|
204912
|
+
* there is nothing to commit outside a repo.
|
|
204913
|
+
*/
|
|
204914
|
+
const assertInitLocation = (git, fs) => gen(function* () {
|
|
204915
|
+
const topLevel = yield* either(git.topLevel());
|
|
204916
|
+
if (topLevel._tag === "Left") return false;
|
|
204917
|
+
if ((yield* fs.realPath(topLevel.right)) !== (yield* fs.realPath(process.cwd()))) return yield* fail$2(/* @__PURE__ */ new Error(`gtd init must be run from the repository root (${topLevel.right}) or from a directory outside any git repository; the current directory is a repository subdirectory: ${process.cwd()}`));
|
|
204918
|
+
return true;
|
|
204919
|
+
});
|
|
204815
204920
|
/** Dispatches to the named `run*Command` handler for every known subcommand. */
|
|
204816
204921
|
const dispatchKnownSubcommand = (sub, argv, json, write, cost, model) => {
|
|
204817
204922
|
switch (sub) {
|
package/docs/configuration.md
CHANGED
|
@@ -92,11 +92,23 @@ engine, and [§3](../STATES.md#3-pattern-grammar) for the pattern grammar.
|
|
|
92
92
|
### Content values: inline or a file reference
|
|
93
93
|
|
|
94
94
|
A `script`/`prompt`/`message`/`commit` value starting with `./` or `../` is a
|
|
95
|
-
**file reference** — resolved relative to the
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
95
|
+
**file reference** — resolved relative to **the directory of the `.gtdrc` that
|
|
96
|
+
declared it** (not the directory `gtd` runs from) and read at load time. A
|
|
97
|
+
missing or unreadable file reference is a **load error**, collected and thrown
|
|
98
|
+
with every other config problem — never silently treated as inline text. Any
|
|
99
|
+
other string (including one that merely contains a `/`, or an absolute path) is
|
|
100
|
+
inline Eta template source, used verbatim.
|
|
101
|
+
|
|
102
|
+
Because references anchor to the declaring file, a `.gtdrc` and its
|
|
103
|
+
`gtd-prompts/` (or `prompts/`) directory can live **outside** the repo — e.g. in
|
|
104
|
+
a parent directory shared across several repos. gtd still runs from the repo
|
|
105
|
+
root, discovers the parent `.gtdrc` by walking up the directory chain (see
|
|
106
|
+
["Lookup and precedence"](#lookup-and-precedence) — cwd→home), and inlines each
|
|
107
|
+
level's `./`-relative references against that level's own directory. When
|
|
108
|
+
workflows are layered across levels, every reference resolves against the file
|
|
109
|
+
that wrote it, so a parent's `./gtd-prompts/x.md` and a child's never collide.
|
|
110
|
+
You can scaffold such a shared config by running [`gtd init`](#gtd-init) from
|
|
111
|
+
that parent directory — it need not be a git repository.
|
|
100
112
|
|
|
101
113
|
```yaml
|
|
102
114
|
workflow:
|
|
@@ -677,6 +689,16 @@ load time, so a prompt is editable Markdown and editing it changes the workflow
|
|
|
677
689
|
with no config edit. Human `message:` blocks and check `script:` bodies stay
|
|
678
690
|
inline in the config (they are workflow mechanics, not prompts).
|
|
679
691
|
|
|
692
|
+
`gtd init` writes only config — it derives no git state — so it need not run in
|
|
693
|
+
a git repository. It runs at a **repository root** or in a directory **outside
|
|
694
|
+
any repository** (scaffolding a shared config a nested repo picks up by walking
|
|
695
|
+
up — see the parent-dir note under
|
|
696
|
+
[file references](#content-values-inline-or-a-file-reference)); its guidance
|
|
697
|
+
adjusts to say there is nothing to commit when it is not in a repo. It refuses
|
|
698
|
+
only to write into a repository **subdirectory**, where the upward config walk
|
|
699
|
+
would never find the file. Either way, it will not overwrite an existing config
|
|
700
|
+
at that directory (remove it first to re-scaffold).
|
|
701
|
+
|
|
680
702
|
It also seeds a top-level [`modes:`](#modes--pluggable-steering-file-modes)
|
|
681
703
|
block as a ready-to-edit suggestion — a Prettier `format:` for each built-in
|
|
682
704
|
steering-file mode the template uses:
|