create-pathfinder 1.8.0 → 2.1.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/AGENTS.md +3 -1
- package/CLAUDE.md +20 -0
- package/README.md +383 -65
- package/context/ai-interaction.md +78 -39
- package/context/coding-standards.md +119 -60
- package/copy-list.json +1 -0
- package/package.json +2 -1
- package/roles/developer.md +37 -0
- package/roles/planner.md +38 -0
- package/roles/tester.md +40 -0
- package/skills/challenge-me/SKILL.md +11 -7
- package/skills/complete-feature/SKILL.md +16 -11
- package/skills/handoff/SKILL.md +24 -4
- package/skills/kickstart-pathfinder/SKILL.md +6 -1
- package/skills/learning-review/SKILL.md +9 -6
- package/skills/load-feature/SKILL.md +40 -10
- package/skills/prototype/SKILL.md +3 -1
- package/skills/quiz-me/SKILL.md +33 -7
- package/skills/review-feature/SKILL.md +19 -11
- package/skills/role/SKILL.md +33 -0
- package/skills/setup-tracker/SKILL.md +29 -61
- package/skills/start-feature/SKILL.md +19 -16
- package/skills/sync-tracker/SKILL.md +67 -106
- package/skills/teach-architecture/SKILL.md +6 -5
- package/skills/teach-feature/SKILL.md +11 -8
- package/skills/to-specs/SKILL.md +40 -28
- package/skills/whereami/SKILL.md +87 -0
- package/src/cli.mjs +67 -96
- package/src/install.mjs +5 -1
- package/src/kit.mjs +70 -1
- package/src/outcome.mjs +147 -0
- package/templates/CHANGELOG.template.md +2 -1
- package/templates/feature-spec.template.md +18 -80
- package/templates/history.template.md +12 -0
- package/templates/lesson.template.md +22 -51
- package/templates/project-overview.template.md +80 -145
- package/context/current-feature.md +0 -46
- package/context/features/example-feature-spec.md +0 -103
- package/context/history.md +0 -14
- package/context/learning/learner-profile.md +0 -35
- package/context/learning/lessons/.gitkeep +0 -0
- package/context/learning/progress.md +0 -28
- package/context/project-overview.md +0 -211
- package/templates/progress-entry.template.md +0 -23
- package/templates/tracker.template.md +0 -359
package/src/kit.mjs
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { existsSync, readFileSync } from "node:fs";
|
|
11
|
-
import { dirname, join, resolve } from "node:path";
|
|
11
|
+
import { dirname, join, relative, resolve, sep } from "node:path";
|
|
12
12
|
import { fileURLToPath } from "node:url";
|
|
13
13
|
|
|
14
14
|
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
@@ -49,6 +49,75 @@ export function isExcluded(basename) {
|
|
|
49
49
|
return EXCLUDED.has(basename) || basename.startsWith("._");
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
/**
|
|
53
|
+
* Kit files that are deliberately not part of the kit, by kit-relative path.
|
|
54
|
+
*
|
|
55
|
+
* A different idea from EXCLUDED above, and kept separate for that reason.
|
|
56
|
+
* Those are OS and editor droppings that were never anybody's file. These are
|
|
57
|
+
* real, hand-written files that live inside a copy-list directory and must
|
|
58
|
+
* still never reach a destination project.
|
|
59
|
+
*
|
|
60
|
+
* All three are this repository's own working state, and every one of them
|
|
61
|
+
* would be actively wrong in somebody else's project.
|
|
62
|
+
*
|
|
63
|
+
* `context/tracker.md` is the original case. Work Tracking's off switch is the
|
|
64
|
+
* *absence* of that file in a destination project, so shipping this
|
|
65
|
+
* repository's own copy would hand every new project a configuration naming a
|
|
66
|
+
* tracker it does not own, pointing at a spec directory it does not have, with
|
|
67
|
+
* the off switch already defeated on first install.
|
|
68
|
+
*
|
|
69
|
+
* `context/current-feature.md` and `context/handoff.md` are transient session
|
|
70
|
+
* state, and they are here for the same reason one step further on: the kit
|
|
71
|
+
* stopped shipping a blank `current-feature.md` stencil, because `load-feature`
|
|
72
|
+
* writes the real one on first use and a placeholder is just a file a reader
|
|
73
|
+
* has to recognise as empty. Having stopped shipping the blank one, the thing
|
|
74
|
+
* to guard against is shipping a *filled-in* one — a destination project
|
|
75
|
+
* opening its first session to a note about whichever feature a Pathfinder
|
|
76
|
+
* maintainer had loaded on the day of the release.
|
|
77
|
+
*
|
|
78
|
+
* `context` is a *directory* entry in the copy list, so anything placed beneath
|
|
79
|
+
* it ships by default. Making the invariant enforced rather than intended is
|
|
80
|
+
* the same move `check_no_junk_tracked` made: an ignore rule is advisory, one
|
|
81
|
+
* `git add -f` defeats it, and `stage-kit.mjs` copies from the working tree
|
|
82
|
+
* without consulting it at all.
|
|
83
|
+
*
|
|
84
|
+
* Matched on the kit-relative path, never the basename — a project's own
|
|
85
|
+
* `tracker.md` somewhere else is not this file and must not be caught by it.
|
|
86
|
+
*/
|
|
87
|
+
const NEVER_SHIPS = new Set([
|
|
88
|
+
"context/tracker.md",
|
|
89
|
+
"context/current-feature.md",
|
|
90
|
+
"context/handoff.md",
|
|
91
|
+
]);
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Is this kit-relative path one the kit must never hand over?
|
|
95
|
+
*
|
|
96
|
+
* @param {string} relativePath forward-slashed, relative to the kit root
|
|
97
|
+
*/
|
|
98
|
+
export function neverShips(relativePath) {
|
|
99
|
+
return NEVER_SHIPS.has(relativePath);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* A `cpSync` filter that drops never-ships files from a recursive copy.
|
|
104
|
+
*
|
|
105
|
+
* Lives here rather than inline in `stage-kit.mjs` so the staging path and its
|
|
106
|
+
* test run the same code. A test that rebuilds the predicate proves only that
|
|
107
|
+
* `cpSync` honours `filter`; it cannot catch the path arithmetic below going
|
|
108
|
+
* wrong, which is the part with anything to get wrong in it.
|
|
109
|
+
*
|
|
110
|
+
* `cpSync` hands the filter absolute paths and calls it for the copy root
|
|
111
|
+
* itself, so the root resolves to `""` and is kept — filtering a directory out
|
|
112
|
+
* would take its whole subtree with it.
|
|
113
|
+
*
|
|
114
|
+
* @param {string} rootDir absolute path the kit-relative paths are relative to
|
|
115
|
+
* @returns {(source: string) => boolean} true to copy, false to skip
|
|
116
|
+
*/
|
|
117
|
+
export function neverShipsFilter(rootDir) {
|
|
118
|
+
return (source) => !neverShips(relative(rootDir, source).split(sep).join("/"));
|
|
119
|
+
}
|
|
120
|
+
|
|
52
121
|
const PACKAGE_ROOT = resolve(HERE, "..");
|
|
53
122
|
|
|
54
123
|
/**
|
package/src/outcome.mjs
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a run did, derived once.
|
|
3
|
+
*
|
|
4
|
+
* Two renderings print this install — `contractReport` owes byte-for-byte what
|
|
5
|
+
* 1.4.1 printed, `expressiveReport` owes a person a legible hierarchy — and
|
|
6
|
+
* they had each grown their own copy of the same four derivations: the
|
|
7
|
+
* mode-dependent written count, the skipped filter, the failure merge, and a
|
|
8
|
+
* per-harness adapter tally that appeared three times character-for-character.
|
|
9
|
+
* Four facts, ten spellings, and no mechanism keeping them in agreement. This
|
|
10
|
+
* module is the one spelling. The renderings stay two renderings; they just
|
|
11
|
+
* stop each deciding what the numbers are.
|
|
12
|
+
*
|
|
13
|
+
* Pure by construction: no filesystem, no `process`, no writing. Everything
|
|
14
|
+
* here is a function of the plans and results it is handed, which is what lets
|
|
15
|
+
* a summary be tested without building a temporary repository or a fake
|
|
16
|
+
* terminal.
|
|
17
|
+
*
|
|
18
|
+
* Two counters elsewhere are deliberately *not* folded in, and a later change
|
|
19
|
+
* that "finishes the job" will break them:
|
|
20
|
+
*
|
|
21
|
+
* - `countWritten` in `cli.mjs` runs mid-run, before adapters are applied, and
|
|
22
|
+
* reports a different number — `written + overwritten`, labelled "copied" —
|
|
23
|
+
* than the summary's `written`. It cannot read a result that does not exist
|
|
24
|
+
* yet.
|
|
25
|
+
* - The streaming per-harness counts in `generateAdapters` accumulate as units
|
|
26
|
+
* resolve, so milestones can print while the work is happening, and they
|
|
27
|
+
* count conflicts, orphans, and up-to-date adapters differently from the
|
|
28
|
+
* rows below. A summary computed at the end cannot drive a progress bar.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Every derived fact both renderings need, and nothing either of them can
|
|
33
|
+
* compute for itself.
|
|
34
|
+
*
|
|
35
|
+
* @param {object} args
|
|
36
|
+
* @param {{relativePath: string, status: "write"|"skip"|"overwrite"}[]} args.plan
|
|
37
|
+
* the kit copy plan, in `planInstall`'s sort
|
|
38
|
+
* @param {{written: number, skipped: number, overwritten: number,
|
|
39
|
+
* errors: {relativePath: string, message: string}[]}} args.result
|
|
40
|
+
* @param {{plan: object[], result: object, blocked: boolean}} args.adapters
|
|
41
|
+
* @param {{label: string}[]} args.harnesses the selected harnesses, registry order
|
|
42
|
+
* @param {{dryRun?: boolean}} args.options
|
|
43
|
+
* @returns {Readonly<object>} frozen; rows and lists frozen with it
|
|
44
|
+
*/
|
|
45
|
+
export function summarize({ plan, result, adapters, harnesses, options }) {
|
|
46
|
+
// A dry run has no `result.written` to report, because nothing was written.
|
|
47
|
+
// The plan is counted instead, which is the same number the run would have
|
|
48
|
+
// produced had it been allowed to write.
|
|
49
|
+
const written = options.dryRun
|
|
50
|
+
? plan.filter((item) => item.status === "write").length
|
|
51
|
+
: result.written;
|
|
52
|
+
|
|
53
|
+
const skipped = plan
|
|
54
|
+
.filter((item) => item.status === "skip")
|
|
55
|
+
.map((item) => item.relativePath);
|
|
56
|
+
|
|
57
|
+
// Copy errors before adapter errors, because that is the order they happened
|
|
58
|
+
// in and the order the failure list has always printed.
|
|
59
|
+
const failures = Object.freeze([...result.errors, ...adapters.result.errors]);
|
|
60
|
+
|
|
61
|
+
return Object.freeze({
|
|
62
|
+
written,
|
|
63
|
+
overwritten: result.overwritten,
|
|
64
|
+
skipped: Object.freeze(skipped),
|
|
65
|
+
// Nothing to write and every file already there. Not the same as `written
|
|
66
|
+
// === 0`, which a partly failed copy also satisfies.
|
|
67
|
+
alreadyInstalled: written === 0 && skipped.length === plan.length,
|
|
68
|
+
failures,
|
|
69
|
+
blocked: adapters.blocked,
|
|
70
|
+
// Summed from the result, never from `harnessRows`. The rows exclude paths
|
|
71
|
+
// that errored and this does not, so the two disagree exactly when a write
|
|
72
|
+
// fails — and this is the number the closing headline speaks for.
|
|
73
|
+
built: adapters.result.generated + adapters.result.replaced,
|
|
74
|
+
attention: attentionCount(adapters),
|
|
75
|
+
harnessRows: harnessRows({ adapters, harnesses }),
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* What actually wants a human: a contested path, or an adapter pointing at a
|
|
81
|
+
* skill that is gone.
|
|
82
|
+
*
|
|
83
|
+
* Skipped files are deliberately not counted. A re-run over an existing install
|
|
84
|
+
* skips every file by design, and calling thirty-six routine skips "things to
|
|
85
|
+
* look at" would turn the one number that should mean something into noise
|
|
86
|
+
* nobody reads twice.
|
|
87
|
+
*
|
|
88
|
+
* Counted across the whole adapter plan rather than across `harnessRows`,
|
|
89
|
+
* errored paths included, because a path that could not be written is still a
|
|
90
|
+
* path somebody has to go and look at.
|
|
91
|
+
*/
|
|
92
|
+
function attentionCount(adapters) {
|
|
93
|
+
if (adapters.blocked) return 0;
|
|
94
|
+
return adapters.plan.filter(
|
|
95
|
+
(item) => item.action === "conflict" || item.action === "orphan",
|
|
96
|
+
).length;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* One row per selected harness, in the order the harnesses were given.
|
|
101
|
+
*
|
|
102
|
+
* The three-way distinction the report depends on is carried by the rows
|
|
103
|
+
* themselves, and all three collapse to a zero if it is lost:
|
|
104
|
+
*
|
|
105
|
+
* - no harness chosen — no rows, `blocked: false`
|
|
106
|
+
* - the kit copy failed — no rows, `blocked: true`
|
|
107
|
+
* - a harness that produced nothing — a row of zeroes
|
|
108
|
+
*
|
|
109
|
+
* The blocked case returns no rows explicitly rather than falling out of an
|
|
110
|
+
* empty plan, so that a harness which was chosen and never reached is never
|
|
111
|
+
* described as having generated zero adapters.
|
|
112
|
+
*
|
|
113
|
+
* Paths that failed to write are excluded from every count and list here: an
|
|
114
|
+
* adapter that could not be written was not generated, is not up to date, and
|
|
115
|
+
* is not a conflict the user can resolve by re-running with `--force`. They are
|
|
116
|
+
* reported once, as failures.
|
|
117
|
+
*/
|
|
118
|
+
function harnessRows({ adapters, harnesses }) {
|
|
119
|
+
if (harnesses.length === 0 || adapters.blocked) return Object.freeze([]);
|
|
120
|
+
|
|
121
|
+
const failed = new Set(adapters.result.errors.map((error) => error.relativePath));
|
|
122
|
+
|
|
123
|
+
return Object.freeze(
|
|
124
|
+
harnesses.map((harness) => {
|
|
125
|
+
// Identity, not label: the harness object on a plan item is the registry
|
|
126
|
+
// entry itself, and two entries could plausibly share a label one day.
|
|
127
|
+
const mine = adapters.plan.filter(
|
|
128
|
+
(item) => item.harness === harness && !failed.has(item.relativePath),
|
|
129
|
+
);
|
|
130
|
+
const count = (action) => mine.filter((item) => item.action === action).length;
|
|
131
|
+
const paths = (action) =>
|
|
132
|
+
Object.freeze(
|
|
133
|
+
mine.filter((item) => item.action === action).map((item) => item.relativePath),
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
return Object.freeze({
|
|
137
|
+
harness,
|
|
138
|
+
generated: count("write"),
|
|
139
|
+
replaced: count("replace"),
|
|
140
|
+
unchanged: count("up-to-date"),
|
|
141
|
+
// `planAdapters` order, which is the order they will be printed in.
|
|
142
|
+
conflicts: paths("conflict"),
|
|
143
|
+
orphans: paths("orphan"),
|
|
144
|
+
});
|
|
145
|
+
}),
|
|
146
|
+
);
|
|
147
|
+
}
|
|
@@ -2,104 +2,42 @@
|
|
|
2
2
|
|
|
3
3
|
## Status
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
## Overview
|
|
8
|
-
|
|
9
|
-
Describe the smallest coherent outcome, where it fits, and why it matters.
|
|
10
|
-
|
|
11
|
-
## Problem
|
|
12
|
-
|
|
13
|
-
- What is missing, risky, confusing, inaccessible, unreliable, or inefficient?
|
|
14
|
-
- Who or what is affected?
|
|
15
|
-
- Why does this matter now?
|
|
5
|
+
Proposed
|
|
16
6
|
|
|
17
7
|
## Goal
|
|
18
8
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
## Dependencies
|
|
9
|
+
[One clear user-visible or system-verifiable outcome.]
|
|
22
10
|
|
|
23
|
-
|
|
11
|
+
## Context
|
|
24
12
|
|
|
25
|
-
|
|
13
|
+
Include only what materially helps someone implement this Feature.
|
|
26
14
|
|
|
27
|
-
-
|
|
28
|
-
-
|
|
29
|
-
-
|
|
15
|
+
- Read: `[specific files, sections, or systems]`
|
|
16
|
+
- Relevant area: `[path or component]`
|
|
17
|
+
- Avoid: `[unrelated area, if useful]`
|
|
30
18
|
|
|
31
19
|
## Requirements
|
|
32
20
|
|
|
33
|
-
-
|
|
34
|
-
-
|
|
35
|
-
-
|
|
36
|
-
- Include relevant failure, permission, loading, empty, retry, responsive, accessibility, operational, or compatibility states only when applicable.
|
|
21
|
+
- `[required behavior]`
|
|
22
|
+
- `[important constraint]`
|
|
23
|
+
- `[relevant edge or failure behavior, when applicable]`
|
|
37
24
|
|
|
38
25
|
## Out of Scope
|
|
39
26
|
|
|
40
|
-
-
|
|
41
|
-
- Later feature, if known
|
|
42
|
-
- Unrelated refactors, dependencies, or polish
|
|
43
|
-
|
|
44
|
-
## Experience or Operational Notes — When Applicable
|
|
45
|
-
|
|
46
|
-
- User interaction, system behavior, responsive behavior, accessibility, observability, performance, security, or operational expectations.
|
|
47
|
-
|
|
48
|
-
## Technical Notes
|
|
49
|
-
|
|
50
|
-
Likely areas:
|
|
51
|
-
|
|
52
|
-
- `[specific path or bounded area]`
|
|
53
|
-
|
|
54
|
-
Implementation constraints:
|
|
55
|
-
|
|
56
|
-
- Follow project context and approved prototype direction.
|
|
57
|
-
- Do not silently resolve open architecture decisions.
|
|
58
|
-
- Keep prototype and production code boundaries explicit.
|
|
59
|
-
|
|
60
|
-
## Context Boundary
|
|
61
|
-
|
|
62
|
-
Read:
|
|
63
|
-
|
|
64
|
-
- `[specific context and code]`
|
|
65
|
-
|
|
66
|
-
Avoid loading:
|
|
67
|
-
|
|
68
|
-
- `[unrelated systems, old specs, generated output]`
|
|
69
|
-
|
|
70
|
-
Split this feature further if its required context is not focused enough for reliable implementation and verification.
|
|
27
|
+
- `[explicit exclusion]`
|
|
71
28
|
|
|
72
29
|
## Delivery Chunks
|
|
73
30
|
|
|
74
|
-
1. `[stable, verifiable increment]`
|
|
75
|
-
2. `[
|
|
76
|
-
3. `[optional stable increment]`
|
|
31
|
+
1. `[small, stable, verifiable increment]`
|
|
32
|
+
2. `[next increment, if needed]`
|
|
77
33
|
|
|
78
|
-
|
|
34
|
+
Use one chunk when the Feature is already small.
|
|
79
35
|
|
|
80
36
|
## Acceptance Criteria
|
|
81
37
|
|
|
82
|
-
-
|
|
83
|
-
-
|
|
84
|
-
- Applicable quality requirements are met.
|
|
85
|
-
- Required automated/manual checks pass.
|
|
86
|
-
- The work can be reviewed independently.
|
|
87
|
-
|
|
88
|
-
## Verification
|
|
89
|
-
|
|
90
|
-
- Manual or operational checks:
|
|
91
|
-
- Automated checks:
|
|
92
|
-
- Edge cases:
|
|
93
|
-
- Quality checks:
|
|
94
|
-
|
|
95
|
-
## Learning Targets
|
|
96
|
-
|
|
97
|
-
- Concepts worth explaining after completion:
|
|
98
|
-
- Diagram or demonstration opportunity:
|
|
99
|
-
- Quiz ideas:
|
|
38
|
+
- `[observable result proving the Feature works]`
|
|
39
|
+
- `[important verification result]`
|
|
100
40
|
|
|
101
|
-
##
|
|
41
|
+
## Notes / Decisions
|
|
102
42
|
|
|
103
|
-
-
|
|
104
|
-
- Suggested branch, when applicable: `[project naming convention]`
|
|
105
|
-
- Suggested commit, when applicable: `[project convention]`
|
|
43
|
+
- `[Feature-specific dependency, approved decision, constraint, or None]`
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Project History
|
|
2
|
+
|
|
3
|
+
Compact record of completed work.
|
|
4
|
+
|
|
5
|
+
## Completed
|
|
6
|
+
|
|
7
|
+
### [YYYY-MM-DD] — [Feature or milestone]
|
|
8
|
+
|
|
9
|
+
- Outcome: `[what changed for the project/user]`
|
|
10
|
+
- Verification: `[brief result or evidence pointer]`
|
|
11
|
+
- Commit/PR: `[sha, PR, or release reference]`
|
|
12
|
+
- Follow-up: `[only if something remains, otherwise none]`
|
|
@@ -1,71 +1,42 @@
|
|
|
1
1
|
# Lesson — [Feature Name]
|
|
2
2
|
|
|
3
3
|
- Date:
|
|
4
|
-
-
|
|
5
|
-
-
|
|
6
|
-
- Commit or diff range:
|
|
7
|
-
- Difficulty:
|
|
8
|
-
- Estimated review time:
|
|
4
|
+
- Feature:
|
|
5
|
+
- Commit/diff:
|
|
9
6
|
|
|
10
|
-
##
|
|
7
|
+
## What Changed
|
|
11
8
|
|
|
12
|
-
|
|
9
|
+
Explain what was implemented and why it matters.
|
|
13
10
|
|
|
14
|
-
##
|
|
11
|
+
## Mental Model
|
|
15
12
|
|
|
16
|
-
Explain the
|
|
13
|
+
Explain the simplest useful way to understand the Feature.
|
|
17
14
|
|
|
18
|
-
|
|
15
|
+
Include a small diagram only when it helps.
|
|
19
16
|
|
|
20
|
-
|
|
17
|
+
## How It Works
|
|
21
18
|
|
|
22
|
-
|
|
19
|
+
Trace one representative flow through the implementation.
|
|
23
20
|
|
|
24
|
-
|
|
25
|
-
| --- | --- | --- |
|
|
21
|
+
## Important Files
|
|
26
22
|
|
|
27
|
-
|
|
23
|
+
| File | Responsibility |
|
|
24
|
+
| --- | --- |
|
|
28
25
|
|
|
29
|
-
|
|
26
|
+
Include only files that materially help understanding.
|
|
30
27
|
|
|
31
|
-
|
|
32
|
-
- Evidence in the repository
|
|
33
|
-
- Benefit
|
|
34
|
-
- Cost
|
|
35
|
-
- Credible alternative
|
|
36
|
-
- When the alternative would be better
|
|
28
|
+
## Key Decisions
|
|
37
29
|
|
|
38
|
-
|
|
30
|
+
- `[important decision and why it was made]`
|
|
39
31
|
|
|
40
|
-
|
|
32
|
+
Include only decisions worth remembering.
|
|
41
33
|
|
|
42
|
-
##
|
|
34
|
+
## Concepts to Retain
|
|
43
35
|
|
|
44
|
-
|
|
36
|
+
- `[transferable concept]`
|
|
45
37
|
|
|
46
|
-
|
|
38
|
+
## Check Your Understanding
|
|
47
39
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
### Error and edge states
|
|
53
|
-
|
|
54
|
-
## 8. Production-Scale Gap
|
|
55
|
-
|
|
56
|
-
What would need to change for higher traffic, larger datasets, multiple teams, stricter security, or real-time collaboration?
|
|
57
|
-
|
|
58
|
-
## 9. Interview Preparation
|
|
59
|
-
|
|
60
|
-
- Likely question:
|
|
61
|
-
- Strong answer outline:
|
|
62
|
-
- Follow-up question:
|
|
63
|
-
- Vocabulary to use carefully:
|
|
64
|
-
|
|
65
|
-
## 10. Check Your Understanding
|
|
66
|
-
|
|
67
|
-
Three short retrieval questions. Do not include answers here.
|
|
68
|
-
|
|
69
|
-
## 11. Suggested Next Action
|
|
70
|
-
|
|
71
|
-
Choose one: quiz, challenge, architecture lesson, spaced review, or no further work.
|
|
40
|
+
1. `[question]`
|
|
41
|
+
2. `[question]`
|
|
42
|
+
3. `[question]`
|