forgedock 1.0.22 → 1.1.2
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 +255 -211
- package/bin/batch-runner.mjs +339 -0
- package/bin/cinema.mjs +320 -0
- package/bin/demo.mjs +92 -7
- package/bin/engine/phases.mjs +168 -0
- package/bin/engine/projector.mjs +32 -0
- package/bin/engine/reconcile.mjs +11 -0
- package/bin/engine/runlog.mjs +79 -0
- package/bin/engine/state.mjs +32 -0
- package/bin/engine-cli.mjs +222 -0
- package/bin/engine.mjs +138 -0
- package/bin/forgedock.mjs +896 -1540
- package/bin/hooks/interactive-engine.mjs +509 -0
- package/bin/hooks/pre-tool-use.mjs +498 -0
- package/bin/hooks/subagent-stop-enforce.mjs +197 -0
- package/bin/journey.mjs +841 -0
- package/bin/registry.mjs +139 -0
- package/bin/report.mjs +447 -0
- package/bin/runner.mjs +136 -20
- package/bin/settings-hook.mjs +346 -0
- package/bin/tui.mjs +6 -3
- package/commands/adopt.md +4 -1
- package/commands/analytics.md +4 -1
- package/commands/audit-agents.md +110 -27
- package/commands/audit.md +20 -1
- package/commands/autopilot.md +84 -12
- package/commands/changelog.md +2 -1
- package/commands/ci-audit.md +2 -1
- package/commands/cleanup.md +161 -1
- package/commands/compat-audit.md +2 -1
- package/commands/deploy-info.md +63 -4
- package/commands/design-bench.md +6 -1
- package/commands/design-render-critique-loop.md +2 -1
- package/commands/design.md +1 -0
- package/commands/diagnose.md +2 -1
- package/commands/explain.md +2 -1
- package/commands/forge-stats.md +122 -2
- package/commands/forgedock-init.md +1 -1
- package/commands/geo-audit.md +3 -0
- package/commands/incident-response.md +2 -1
- package/commands/issue.md +24 -3
- package/commands/milestone.md +77 -12
- package/commands/optimize.md +2 -1
- package/commands/orchestrate/config.md +67 -0
- package/commands/orchestrate/phase-1-resolve.md +159 -0
- package/commands/orchestrate/phase-2-triage.md +126 -0
- package/commands/orchestrate/phase-2.5-synthesis.md +156 -0
- package/commands/orchestrate/phase-3-dependency.md +591 -0
- package/commands/orchestrate/phase-4-execution.md +962 -0
- package/commands/orchestrate/phase-5-cleanup.md +47 -0
- package/commands/orchestrate/phase-6-report.md +198 -0
- package/commands/orchestrate/safety.md +62 -0
- package/commands/orchestrate.md +25 -2027
- package/commands/pipeline-health/config.md +34 -0
- package/commands/pipeline-health/phase-1-context.md +180 -0
- package/commands/pipeline-health/phase-2-metrics.md +1729 -0
- package/commands/pipeline-health/phase-3-analyze.md +204 -0
- package/commands/pipeline-health/phase-4-proposals.md +117 -0
- package/commands/pipeline-health/phase-5-report.md +284 -0
- package/commands/pipeline-health/phase-6-summary.md +52 -0
- package/commands/pipeline-health.md +25 -2401
- package/commands/pipeline-resume.md +24 -1
- package/commands/pipeline-status.md +65 -0
- package/commands/qa-sweep.md +4 -1
- package/commands/quality-gate.md +326 -42
- package/commands/replay.md +2 -1
- package/commands/review-pr-agents/api.md +135 -0
- package/commands/review-pr-agents/auth.md +137 -0
- package/commands/review-pr-agents/billing.md +101 -0
- package/commands/review-pr-agents/concurrency.md +145 -0
- package/commands/review-pr-agents/database.md +103 -0
- package/commands/review-pr-agents/frontend.md +89 -0
- package/commands/review-pr-agents/infra.md +385 -0
- package/commands/review-pr-agents/protocols.md +185 -0
- package/commands/review-pr-agents/scraper.md +185 -0
- package/commands/review-pr-agents/security.md +406 -0
- package/commands/review-pr-agents.md +45 -1722
- package/commands/review-pr-staging.md +196 -40
- package/commands/review-pr.md +612 -56
- package/commands/rollback.md +2 -1
- package/commands/scope.md +2 -1
- package/commands/security-audit.md +2 -1
- package/commands/signal-planner.md +623 -0
- package/commands/sync-ecosystem.md +1 -0
- package/commands/test-gate.md +17 -1
- package/commands/validate.md +2 -1
- package/commands/work-on/build/architect.md +28 -0
- package/commands/work-on/build/context.md +2 -0
- package/commands/work-on/build/implement.md +19 -5
- package/commands/work-on/build/validate.md +62 -6
- package/commands/work-on/build.md +187 -30
- package/commands/work-on/close.md +132 -5
- package/commands/work-on/decompose.md +19 -1
- package/commands/work-on/investigate.md +117 -2
- package/commands/work-on/review.md +48 -4
- package/commands/work-on-monolithic.md +8 -2
- package/commands/work-on.md +247 -7
- package/docs/BRAND.md +16 -5
- package/docs/CI.md +2 -2
- package/docs/CODEX.md +36 -1
- package/docs/CONFIG.md +44 -42
- package/docs/FORGE-PROTOCOL.md +15 -1064
- package/docs/anthropic-devrel-outreach.md +2 -2
- package/docs/articles/command-decomposition-token-savings.md +214 -0
- package/docs/articles/model-release-playbook.md +131 -0
- package/docs/articles/per-repo-adaptive-scripts-token-savings.md +27 -25
- package/docs/claude-breakpoints.json +75 -5
- package/docs/design/abc-benchmark.md +1 -1
- package/docs/design/design-spec-schema.md +1 -1
- package/docs/design/divergent-generation.md +1 -1
- package/docs/design/eval-gate-runbook.md +178 -0
- package/docs/design/show-hn-draft.md +26 -1
- package/docs/design/token-cost-baseline-1298.md +59 -0
- package/docs/eval/README.md +117 -0
- package/docs/eval/corpus/seed-v1.json +6 -0
- package/docs/eval/published/claude-sonnet-4-6-2026-07-04.json +33 -0
- package/docs/site/annotations-explained.md +24 -0
- package/docs/site/command-learning-path.md +51 -14
- package/docs/site/command-reference.md +339 -113
- package/docs/site/for-companies.md +95 -0
- package/docs/site/forge-annotation-protocol.md +12 -256
- package/docs/site/getting-started.md +58 -12
- package/docs/site/github-is-the-memory.md +113 -0
- package/docs/site/how-it-works.md +3 -13
- package/docs/site/index.md +10 -2
- package/docs/spec/eval-run-result.md +147 -0
- package/docs/spec/forge-protocol-v1.md +441 -18
- package/docs/spec/label-state-machine.md +55 -0
- package/docs/spec/review-protocol.md +158 -0
- package/docs/spec-graph-schema.md +28 -0
- package/docs/superpowers/plans/2026-07-03-cinematic-onboarding-tui.md +2339 -0
- package/docs/superpowers/plans/2026-07-03-durable-execution-engine.md +1223 -0
- package/docs/superpowers/specs/2026-07-03-cinematic-onboarding-tui-design.md +157 -0
- package/docs/superpowers/specs/2026-07-03-durable-execution-engine-design.md +194 -0
- package/docs/superpowers/specs/2026-07-04-economic-self-governance-design.md +181 -0
- package/docs/superpowers/specs/2026-07-04-provenance-trail-design.md +189 -0
- package/examples/forgedock-demo/README.md +41 -22
- package/examples/forgedock-demo/issues/06-bug-tags-type-validation.md +43 -0
- package/examples/forgedock-demo/issues/07-bug-negative-offset-pagination.md +42 -0
- package/examples/forgedock-demo/issues/08-bug-patch-mass-assignment.md +49 -0
- package/examples/forgedock-demo/issues/09-bug-count-injection.md +47 -0
- package/examples/forgedock-demo/issues/10-feature-sort-param.md +39 -0
- package/examples/forgedock-demo/issues/11-feature-patch-tags.md +37 -0
- package/examples/forgedock-demo/issues/12-feature-bulk-create.md +42 -0
- package/examples/forgedock-demo/issues/13-feature-archive-notes.md +46 -0
- package/examples/forgedock-demo/issues/14-refactor-extract-title-validation.md +41 -0
- package/examples/forgedock-demo/issues/15-refactor-protected-route-flag.md +43 -0
- package/examples/forgedock-demo/issues/16-refactor-extract-store.md +45 -0
- package/examples/forgedock-demo/issues/17-perf-tags-dedup-set.md +35 -0
- package/examples/forgedock-demo/issues/18-perf-remove-redundant-sort.md +37 -0
- package/examples/forgedock-demo/issues/19-docs-api-reference-full.md +53 -0
- package/examples/forgedock-demo/issues/20-docs-intentional-flaw-convention.md +46 -0
- package/examples/forgedock-demo/scripts/smoke.js +18 -1
- package/examples/forgedock-demo/src/db.js +36 -11
- package/examples/forgedock-demo/src/routes/notes.js +106 -9
- package/examples/forgedock-demo/src/server.js +4 -1
- package/package.json +12 -4
- package/packages/protocol/LICENSE +21 -0
- package/packages/protocol/src/cli.js +135 -0
- package/packages/protocol/src/emit.js +146 -0
- package/packages/protocol/src/index.js +43 -0
- package/packages/protocol/src/parse.js +229 -0
- package/packages/protocol/src/types.js +171 -0
- package/packages/protocol/src/validate.js +122 -0
- package/scripts/bench-topology-cost.mjs +372 -0
- package/scripts/check-command-docs-drift.sh +198 -0
- package/scripts/check-protocol-restatements.sh +176 -0
- package/scripts/check-registry/README.md +72 -0
- package/scripts/check-registry/manifest.json +6 -0
- package/scripts/code-index.sh +454 -0
- package/scripts/conformance-check.mjs +168 -0
- package/scripts/doctor-pipeline-state.sh +484 -0
- package/scripts/eval-gate-baseline.json +17 -0
- package/scripts/eval-gate-scorecard.mjs +242 -0
- package/scripts/eval-scorecard-aggregator.mjs +252 -0
- package/scripts/flaky-quarantine.sh +241 -0
- package/scripts/forge-annotation.sh +567 -0
- package/scripts/issue-dedup.sh +236 -0
- package/scripts/validate-annotation-node.mjs +233 -0
- package/scripts/verify-env-vars.sh +18 -2
- package/scripts/verify-host-headers.sh +4 -4
- package/scripts/verify-route-registration.sh +2 -2
- package/scripts/worktree-lifecycle.sh +202 -0
- package/templates/devdocs/agent/using-forgedock.md +14 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "refactor(notes): extract title validation into a shared validate.js module"
|
|
3
|
+
labels: ["refactor", "priority:P3"]
|
|
4
|
+
difficulty: "easy"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Problem
|
|
8
|
+
|
|
9
|
+
`createNote()` in `src/routes/notes.js` has an inline `if (!title)` guard. When
|
|
10
|
+
the bulk-create endpoint (issue #12) is added, the same check will need to be
|
|
11
|
+
duplicated. Any future handler that creates or modifies a note title will face
|
|
12
|
+
the same duplication.
|
|
13
|
+
|
|
14
|
+
This is a pure refactor — no behavior change, no new validation rules.
|
|
15
|
+
|
|
16
|
+
## Proposed Solution
|
|
17
|
+
|
|
18
|
+
Extract the title-presence check into a new `src/validate.js` module that
|
|
19
|
+
exports a single function: `validateNote({ title })`. The function throws a
|
|
20
|
+
`400`-status error if `title` is falsy, identical to the current inline guard.
|
|
21
|
+
`createNote()` (and, if issue #12 is already merged, `bulkCreateNotes()`) should
|
|
22
|
+
call `validateNote()` instead of the inline check.
|
|
23
|
+
|
|
24
|
+
## Affected Files
|
|
25
|
+
|
|
26
|
+
1. `src/validate.js` (NEW) — exports `validateNote({ title })`.
|
|
27
|
+
2. `src/routes/notes.js` — replace the inline `if (!title)` block with
|
|
28
|
+
`validateNote({ title })` imported from `../validate`.
|
|
29
|
+
|
|
30
|
+
## Acceptance Criteria
|
|
31
|
+
|
|
32
|
+
- [ ] `POST /notes` without a `title` still returns `400` with the same error
|
|
33
|
+
message as before.
|
|
34
|
+
- [ ] `src/routes/notes.js` no longer contains a bare `if (!title)` guard —
|
|
35
|
+
that logic lives only in `src/validate.js`.
|
|
36
|
+
- [ ] `npm run smoke` still passes.
|
|
37
|
+
- [ ] No behavior change in any endpoint.
|
|
38
|
+
|
|
39
|
+
> A zero-behavior-change refactor. The review agents should confirm that the
|
|
40
|
+
> 400 path, the error message, and all other responses are byte-for-byte
|
|
41
|
+
> identical to the pre-refactor baseline.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "refactor(server): centralize auth enforcement with a protected flag in the route table"
|
|
3
|
+
labels: ["refactor", "priority:P3"]
|
|
4
|
+
difficulty: "medium"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Problem
|
|
8
|
+
|
|
9
|
+
Authorization (`requireToken`) is enforced by each route handler individually.
|
|
10
|
+
As routes grow, there is no single place to see which routes are protected — a
|
|
11
|
+
reviewer must read every handler. Missing a `requireToken` call in a new handler
|
|
12
|
+
is exactly how issue #1's bug was introduced.
|
|
13
|
+
|
|
14
|
+
## Proposed Solution
|
|
15
|
+
|
|
16
|
+
Add a boolean `protected` flag as the fourth element of each route-table entry
|
|
17
|
+
in `src/server.js`. The server's request dispatch loop should call
|
|
18
|
+
`requireToken(req)` centrally, before the handler is invoked, whenever the flag
|
|
19
|
+
is `true`. Handlers that currently call `requireToken()` themselves should have
|
|
20
|
+
that call removed (it is now redundant).
|
|
21
|
+
|
|
22
|
+
This is a pure refactor — every endpoint's authorization behavior must remain
|
|
23
|
+
identical.
|
|
24
|
+
|
|
25
|
+
## Affected Files
|
|
26
|
+
|
|
27
|
+
1. `src/server.js` — add `protected: true/false` to each TABLE entry; call
|
|
28
|
+
`requireToken(req)` in the dispatch loop when the flag is set.
|
|
29
|
+
2. `src/routes/notes.js` — remove all `requireToken(req)` calls from handlers
|
|
30
|
+
that will be protected by the table flag instead.
|
|
31
|
+
|
|
32
|
+
## Acceptance Criteria
|
|
33
|
+
|
|
34
|
+
- [ ] `POST /notes` without a token still returns `401`.
|
|
35
|
+
- [ ] `PATCH /notes/:id` without a token still returns `401`.
|
|
36
|
+
- [ ] `DELETE /notes/:id` still returns its current response (intentionally
|
|
37
|
+
unprotected in the baseline — issue #1 is not yet fixed).
|
|
38
|
+
- [ ] No `requireToken(req)` call remains inside any route handler.
|
|
39
|
+
- [ ] `npm run smoke` still passes.
|
|
40
|
+
- [ ] No behavior change in any endpoint.
|
|
41
|
+
|
|
42
|
+
> This refactor makes the authorization surface visible at a glance —
|
|
43
|
+
> a pattern that directly prevents the class of bug introduced in issue #1.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "refactor(db): extract the generic array-store machinery into store.js"
|
|
3
|
+
labels: ["refactor", "priority:P3"]
|
|
4
|
+
difficulty: "hard"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Problem
|
|
8
|
+
|
|
9
|
+
`src/db.js` conflates two things: the generic in-memory array/index operations
|
|
10
|
+
(`all`, `findById`, `insert`, `remove`, `reset`) and the Notes-specific seed
|
|
11
|
+
data and field defaults. If a second resource (e.g. users) were added, all of
|
|
12
|
+
that generic machinery would need to be duplicated.
|
|
13
|
+
|
|
14
|
+
This is a pure refactor — no behavior change, no new features.
|
|
15
|
+
|
|
16
|
+
## Proposed Solution
|
|
17
|
+
|
|
18
|
+
Extract the generic store machinery into `src/store.js`, exporting a factory
|
|
19
|
+
function `createStore(seed, defaults)`. The returned object exposes:
|
|
20
|
+
`all`, `findById`, `insert`, `remove`, `reset`.
|
|
21
|
+
|
|
22
|
+
`src/db.js` becomes a thin wrapper: it calls `createStore(SEED, { secret:
|
|
23
|
+
false, tags: [], archived: false })` and re-exports the resulting methods plus
|
|
24
|
+
`query` and `bubbleSortById` (which remain in `db.js` as Notes-specific logic).
|
|
25
|
+
|
|
26
|
+
The public API visible to `routes/notes.js` must be byte-for-byte compatible.
|
|
27
|
+
|
|
28
|
+
## Affected Files
|
|
29
|
+
|
|
30
|
+
1. `src/store.js` (NEW) — generic `createStore(seed, defaults)` factory.
|
|
31
|
+
2. `src/db.js` — delegates `all`/`findById`/`insert`/`remove`/`reset` to the
|
|
32
|
+
store instance; keeps `query`/`bubbleSortById`.
|
|
33
|
+
|
|
34
|
+
## Acceptance Criteria
|
|
35
|
+
|
|
36
|
+
- [ ] `db.all()`, `db.findById()`, `db.insert()`, `db.remove()`, `db.reset()`
|
|
37
|
+
all behave identically to today.
|
|
38
|
+
- [ ] `db.query()` and `db.bubbleSortById()` are unchanged.
|
|
39
|
+
- [ ] No code in `src/routes/notes.js` or `src/server.js` changes.
|
|
40
|
+
- [ ] `npm run smoke` still passes.
|
|
41
|
+
- [ ] No behavior change in any endpoint.
|
|
42
|
+
|
|
43
|
+
> A structural refactor that introduces an abstraction boundary — the review
|
|
44
|
+
> agents should verify that the public interface is preserved exactly and that
|
|
45
|
+
> no observable behavior changes.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "perf(notes): replace O(n²) tag de-duplication in listTags with a Set"
|
|
3
|
+
labels: ["performance", "priority:P3"]
|
|
4
|
+
difficulty: "easy"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Problem
|
|
8
|
+
|
|
9
|
+
`GET /notes/tags` de-duplicates tags using a nested loop: for every tag in every
|
|
10
|
+
note it scans the entire `unique` array to check for a duplicate. This is O(n²)
|
|
11
|
+
on the total tag count. With a large corpus of notes and many tags, this becomes
|
|
12
|
+
slow.
|
|
13
|
+
|
|
14
|
+
The fix is a one-line change to use a `Set` for deduplication instead.
|
|
15
|
+
|
|
16
|
+
## Investigation Hint
|
|
17
|
+
|
|
18
|
+
Before optimizing, confirm where the nested loop is and calculate the actual
|
|
19
|
+
time complexity. Verify that the `Set`-based replacement produces the same
|
|
20
|
+
output (same tags, same order — first-seen wins, matching the current
|
|
21
|
+
behavior).
|
|
22
|
+
|
|
23
|
+
## Affected Files
|
|
24
|
+
|
|
25
|
+
1. `src/routes/notes.js` — `listTags()`: replace the nested-loop uniqueness
|
|
26
|
+
check with a `Set`.
|
|
27
|
+
|
|
28
|
+
## Acceptance Criteria
|
|
29
|
+
|
|
30
|
+
- [ ] `GET /notes/tags` returns the same tags in first-seen order as before.
|
|
31
|
+
- [ ] The nested loop in `listTags()` is removed.
|
|
32
|
+
- [ ] `npm run smoke` still passes.
|
|
33
|
+
|
|
34
|
+
> A focused, single-function perf fix. The pipeline should confirm output
|
|
35
|
+
> equivalence before and after the change, not just that the test passes.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "perf(notes): remove unnecessary O(n²) sort before pagination slice"
|
|
3
|
+
labels: ["performance", "priority:P3"]
|
|
4
|
+
difficulty: "medium"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Problem
|
|
8
|
+
|
|
9
|
+
`GET /notes?limit=...&offset=...` runs `db.bubbleSortById(results)` before
|
|
10
|
+
slicing — an O(n²) bubble sort — even though `results` is already in id order
|
|
11
|
+
(the in-memory store is maintained in insertion order and ids are
|
|
12
|
+
monotonically increasing). The sort is both redundant and slow.
|
|
13
|
+
|
|
14
|
+
## Investigation Hint
|
|
15
|
+
|
|
16
|
+
Trace the insertion order invariant through `db.insert()` and `db.reset()` to
|
|
17
|
+
confirm that `notes` is always in id-ascending order. Then verify that
|
|
18
|
+
`db.bubbleSortById` does not change the element order for already-sorted input
|
|
19
|
+
(it should not — confirm this, don't assume it). Once confirmed, the sort call
|
|
20
|
+
is safe to remove.
|
|
21
|
+
|
|
22
|
+
## Affected Files
|
|
23
|
+
|
|
24
|
+
1. `src/routes/notes.js` — `listNotes()`: remove the `db.bubbleSortById(results)`
|
|
25
|
+
call in the pagination branch. Slice `results` directly.
|
|
26
|
+
|
|
27
|
+
## Acceptance Criteria
|
|
28
|
+
|
|
29
|
+
- [ ] `GET /notes?limit=2` returns the same two notes as before (first two by
|
|
30
|
+
id).
|
|
31
|
+
- [ ] `GET /notes?offset=1&limit=1` returns the same note as before.
|
|
32
|
+
- [ ] The `db.bubbleSortById` call is no longer present in `listNotes()`.
|
|
33
|
+
- [ ] `npm run smoke` still passes.
|
|
34
|
+
|
|
35
|
+
> This issue showcases the **investigation phase confirming an invariant** —
|
|
36
|
+
> the pipeline must verify the insertion-order guarantee before removing the
|
|
37
|
+
> sort, rather than blindly deleting it.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "docs(api): add complete API reference covering all endpoints"
|
|
3
|
+
labels: ["documentation", "priority:P3"]
|
|
4
|
+
difficulty: "easy"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Problem
|
|
8
|
+
|
|
9
|
+
The demo API now has 9 endpoints (health, list, count, tags, get, create, patch,
|
|
10
|
+
delete, and the bulk/archive/restore endpoints added by later issues) but only
|
|
11
|
+
the original 5 are described in issue #5's `docs/API.md` — and issue #5 may not
|
|
12
|
+
have been run yet. Either way, the current baseline has no `docs/API.md`, or an
|
|
13
|
+
incomplete one.
|
|
14
|
+
|
|
15
|
+
## Proposed Solution
|
|
16
|
+
|
|
17
|
+
Create (or rewrite) `docs/API.md` documenting all endpoints in the **current
|
|
18
|
+
baseline** (i.e. after the source-code changes in this PR land):
|
|
19
|
+
|
|
20
|
+
| Method | Path | Auth | Description |
|
|
21
|
+
|--------|------|------|-------------|
|
|
22
|
+
| GET | `/health` | no | Liveness check |
|
|
23
|
+
| GET | `/notes` | no | List notes (supports `?where=`, `?tag=`, `?limit=`, `?offset=`) |
|
|
24
|
+
| GET | `/notes/count` | no | Count notes (supports `?owner=`, `?secret=`) |
|
|
25
|
+
| GET | `/notes/tags` | no | List all unique tags |
|
|
26
|
+
| GET | `/notes/:id` | no | Fetch one note |
|
|
27
|
+
| POST | `/notes` | yes | Create a note |
|
|
28
|
+
| PATCH | `/notes/:id` | yes | Update title/body |
|
|
29
|
+
| DELETE | `/notes/:id` | no* | Delete a note |
|
|
30
|
+
|
|
31
|
+
Include a `curl` example for each endpoint, note the intentional flaws inline
|
|
32
|
+
(e.g. `?where=` is injection-vulnerable — see issue #2), and describe the
|
|
33
|
+
`Authorization: Bearer demo-token` header.
|
|
34
|
+
|
|
35
|
+
`*` DELETE is intentionally unprotected — document this as a known flaw (issue #1).
|
|
36
|
+
|
|
37
|
+
## Affected Files
|
|
38
|
+
|
|
39
|
+
1. `docs/API.md` (NEW or REWRITE) — full endpoint reference.
|
|
40
|
+
2. `README.md` — add a "API reference: docs/API.md" link in the "What's inside"
|
|
41
|
+
section if not already present.
|
|
42
|
+
|
|
43
|
+
## Acceptance Criteria
|
|
44
|
+
|
|
45
|
+
- [ ] `docs/API.md` documents all 8 baseline endpoints with method, path, auth
|
|
46
|
+
requirement, description, and a `curl` example.
|
|
47
|
+
- [ ] The intentional flaws (injection, missing auth on DELETE) are called out
|
|
48
|
+
inline with references to the corresponding issue numbers.
|
|
49
|
+
- [ ] `README.md` links to `docs/API.md`.
|
|
50
|
+
- [ ] No code changes required.
|
|
51
|
+
|
|
52
|
+
> Non-code work that still goes through the full pipeline — investigate the
|
|
53
|
+
> current route surface, write accurate docs, review for completeness.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "docs(demo): document the intentional-flaw and graded-issue authoring convention"
|
|
3
|
+
labels: ["documentation", "priority:P3"]
|
|
4
|
+
difficulty: "easy"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Problem
|
|
8
|
+
|
|
9
|
+
The demo corpus relies on a deliberate convention — intentional flaws in source
|
|
10
|
+
code, graded issue difficulty, DEMO NOTE comments — but this convention is
|
|
11
|
+
undocumented outside of the source code comments themselves. Someone contributing
|
|
12
|
+
new issues, or using this corpus as a template for their own eval harness, has no
|
|
13
|
+
single reference explaining how the pieces fit together.
|
|
14
|
+
|
|
15
|
+
## Proposed Solution
|
|
16
|
+
|
|
17
|
+
Create `docs/CORPUS-AUTHORING.md` that explains:
|
|
18
|
+
|
|
19
|
+
1. **The intentional-flaw pattern** — why flaws are introduced in baseline
|
|
20
|
+
source code and how to mark them with `>>> DEMO NOTE:` comments.
|
|
21
|
+
2. **The graded-difficulty taxonomy** — the five categories (bug, feature,
|
|
22
|
+
refactor, perf, docs) and the three difficulty tiers (easy, medium, hard),
|
|
23
|
+
with one example per category from this corpus.
|
|
24
|
+
3. **Issue spec format** — the required YAML frontmatter fields (`title`,
|
|
25
|
+
`labels`, `difficulty`), the required markdown sections (`## Problem`,
|
|
26
|
+
`## Affected Files`, `## Acceptance Criteria`), and the closing `> ...`
|
|
27
|
+
callout paragraph.
|
|
28
|
+
4. **Independence constraint** — how to verify that a new issue is resolvable
|
|
29
|
+
against the baseline commit without depending on other unmerged issues.
|
|
30
|
+
|
|
31
|
+
## Affected Files
|
|
32
|
+
|
|
33
|
+
1. `docs/CORPUS-AUTHORING.md` (NEW).
|
|
34
|
+
2. `README.md` — add a link to `docs/CORPUS-AUTHORING.md` in a "Contributing
|
|
35
|
+
issues" sub-section.
|
|
36
|
+
|
|
37
|
+
## Acceptance Criteria
|
|
38
|
+
|
|
39
|
+
- [ ] `docs/CORPUS-AUTHORING.md` covers all four sections listed above.
|
|
40
|
+
- [ ] Each section references at least one existing issue from the corpus as a
|
|
41
|
+
concrete example.
|
|
42
|
+
- [ ] `README.md` links to the new doc.
|
|
43
|
+
- [ ] No code changes required.
|
|
44
|
+
|
|
45
|
+
> A meta-doc that makes the demo self-explaining. Useful for anyone wanting to
|
|
46
|
+
> extend the corpus or use it as a template for their own benchmark.
|
|
@@ -48,8 +48,25 @@ async function main() {
|
|
|
48
48
|
const unauth = await request(port, 'POST', '/notes', { json: { title: 'nope' } });
|
|
49
49
|
assert.strictEqual(unauth.status, 401, 'POST without token returns 401');
|
|
50
50
|
|
|
51
|
+
const count = await request(port, 'GET', '/notes/count');
|
|
52
|
+
assert.strictEqual(typeof count.body.count, 'number', 'GET /notes/count returns a number');
|
|
53
|
+
|
|
54
|
+
const tags = await request(port, 'GET', '/notes/tags');
|
|
55
|
+
assert.ok(Array.isArray(tags.body.tags), 'GET /notes/tags returns an array');
|
|
56
|
+
|
|
57
|
+
const page = await request(port, 'GET', '/notes?limit=1');
|
|
58
|
+
assert.ok(page.body.notes.length <= 1, 'GET /notes?limit=1 returns at most one note');
|
|
59
|
+
|
|
60
|
+
const noteId = created.body.note.id;
|
|
61
|
+
const patched = await request(port, 'PATCH', `/notes/${noteId}`, {
|
|
62
|
+
token: 'demo-token',
|
|
63
|
+
json: { title: 'smoke (patched)' },
|
|
64
|
+
});
|
|
65
|
+
assert.strictEqual(patched.status, 200, 'PATCH with token returns 200');
|
|
66
|
+
assert.strictEqual(patched.body.note.title, 'smoke (patched)', 'PATCH updates the title');
|
|
67
|
+
|
|
51
68
|
// eslint-disable-next-line no-console
|
|
52
|
-
console.log('SMOKE OK — health, list, create(auth), create(unauth) all pass');
|
|
69
|
+
console.log('SMOKE OK — health, list, create(auth), create(unauth), count, tags, pagination, patch all pass');
|
|
53
70
|
server.close();
|
|
54
71
|
}
|
|
55
72
|
|
|
@@ -10,12 +10,14 @@
|
|
|
10
10
|
// >>> It exists only inside examples/forgedock-demo so the review agents have
|
|
11
11
|
// >>> something to catch. Do not copy this pattern into real code.
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
{ id: 1, owner: 'alice', title: 'Buy milk', body: 'Whole milk, 2 liters', secret: false },
|
|
15
|
-
{ id: 2, owner: 'alice', title: 'Launch plan', body: 'Ship the demo repo', secret: true },
|
|
16
|
-
{ id: 3, owner: 'bob', title: 'Gym', body: 'Leg day', secret: false },
|
|
13
|
+
const SEED = [
|
|
14
|
+
{ id: 1, owner: 'alice', title: 'Buy milk', body: 'Whole milk, 2 liters', secret: false, tags: ['errand'], archived: false, createdAt: '2024-01-01T00:00:00.000Z' },
|
|
15
|
+
{ id: 2, owner: 'alice', title: 'Launch plan', body: 'Ship the demo repo', secret: true, tags: ['work'], archived: false, createdAt: '2024-01-02T00:00:00.000Z' },
|
|
16
|
+
{ id: 3, owner: 'bob', title: 'Gym', body: 'Leg day', secret: false, tags: ['fitness', 'errand'], archived: false, createdAt: '2024-01-03T00:00:00.000Z' },
|
|
17
17
|
];
|
|
18
18
|
|
|
19
|
+
let notes = SEED.map((n) => ({ ...n }));
|
|
20
|
+
|
|
19
21
|
let nextId = 4;
|
|
20
22
|
|
|
21
23
|
function all() {
|
|
@@ -43,7 +45,13 @@ function query(whereClause) {
|
|
|
43
45
|
}
|
|
44
46
|
|
|
45
47
|
function insert(note) {
|
|
46
|
-
const record = { id: nextId++, secret: false,
|
|
48
|
+
const record = { id: nextId++, secret: false, tags: [], archived: false, createdAt: new Date().toISOString() };
|
|
49
|
+
// Only override a default when the caller actually supplied the key — a
|
|
50
|
+
// spread of an object containing an explicit `undefined` would otherwise
|
|
51
|
+
// silently clobber the default (e.g. `tags: undefined`).
|
|
52
|
+
for (const [key, value] of Object.entries(note)) {
|
|
53
|
+
if (value !== undefined) record[key] = value;
|
|
54
|
+
}
|
|
47
55
|
notes.push(record);
|
|
48
56
|
return record;
|
|
49
57
|
}
|
|
@@ -55,12 +63,29 @@ function remove(id) {
|
|
|
55
63
|
}
|
|
56
64
|
|
|
57
65
|
function reset() {
|
|
58
|
-
notes =
|
|
59
|
-
{ id: 1, owner: 'alice', title: 'Buy milk', body: 'Whole milk, 2 liters', secret: false },
|
|
60
|
-
{ id: 2, owner: 'alice', title: 'Launch plan', body: 'Ship the demo repo', secret: true },
|
|
61
|
-
{ id: 3, owner: 'bob', title: 'Gym', body: 'Leg day', secret: false },
|
|
62
|
-
];
|
|
66
|
+
notes = SEED.map((n) => ({ ...n }));
|
|
63
67
|
nextId = 4;
|
|
64
68
|
}
|
|
65
69
|
|
|
66
|
-
|
|
70
|
+
// Returns a NEW array sorted by `id` ascending — does not mutate `arr`.
|
|
71
|
+
//
|
|
72
|
+
// >>> DEMO NOTE: bubble sort is an INTENTIONAL, isolated inefficiency (O(n^2))
|
|
73
|
+
// >>> kept here so the review/perf pipeline has a real hot path to optimize.
|
|
74
|
+
// >>> `notes` is already maintained in insertion (id) order, so callers that
|
|
75
|
+
// >>> only need "sorted by id" rarely need this at all — see the perf issue
|
|
76
|
+
// >>> that targets its one caller in routes/notes.js.
|
|
77
|
+
function bubbleSortById(arr) {
|
|
78
|
+
const copy = arr.slice();
|
|
79
|
+
for (let i = 0; i < copy.length; i++) {
|
|
80
|
+
for (let j = 0; j < copy.length - i - 1; j++) {
|
|
81
|
+
if (copy[j].id > copy[j + 1].id) {
|
|
82
|
+
const tmp = copy[j];
|
|
83
|
+
copy[j] = copy[j + 1];
|
|
84
|
+
copy[j + 1] = tmp;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return copy;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
module.exports = { all, findById, query, insert, remove, reset, bubbleSortById };
|
|
@@ -8,17 +8,80 @@ const db = require('../db');
|
|
|
8
8
|
const { requireToken } = require('../auth');
|
|
9
9
|
|
|
10
10
|
// GET /notes
|
|
11
|
-
// Lists notes. Supports
|
|
12
|
-
// into db.query().
|
|
11
|
+
// Lists notes. Supports:
|
|
12
|
+
// ?where= — raw expression passed straight into db.query() (see db.js)
|
|
13
|
+
// ?tag= — exact match against a note's `tags` array
|
|
14
|
+
// ?limit=, ?offset= — pagination
|
|
13
15
|
//
|
|
14
16
|
// >>> DEMO NOTE: forwarding the raw ?where= string into db.query() is the
|
|
15
17
|
// >>> injection surface (see db.js). Issue #2 asks to make this safe.
|
|
16
18
|
function listNotes(req, res, params) {
|
|
17
|
-
const where = params.query
|
|
18
|
-
|
|
19
|
-
|
|
19
|
+
const { where, tag, limit, offset } = params.query;
|
|
20
|
+
|
|
21
|
+
let results = where ? db.query(where) : db.all();
|
|
22
|
+
|
|
23
|
+
if (tag) {
|
|
24
|
+
// >>> DEMO NOTE: `tags` is accepted but never validated as an array at
|
|
25
|
+
// >>> write time (see createNote below). A note created with a
|
|
26
|
+
// >>> malformed `tags` value makes this `.includes()` call throw instead
|
|
27
|
+
// >>> of being skipped safely. INTENTIONAL, isolated flaw.
|
|
28
|
+
results = results.filter((n) => n.tags.includes(tag));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (limit !== undefined || offset !== undefined) {
|
|
32
|
+
// >>> DEMO NOTE: `offset` is not clamped to a non-negative value, so a
|
|
33
|
+
// >>> negative offset falls through to Array.prototype.slice's
|
|
34
|
+
// >>> negative-index semantics (counts from the end) instead of being
|
|
35
|
+
// >>> rejected or normalized to 0. INTENTIONAL, isolated flaw.
|
|
36
|
+
const off = Number(offset) || 0;
|
|
37
|
+
const lim = Number(limit);
|
|
38
|
+
// Sorting by id here is redundant — `results` is already in id order —
|
|
39
|
+
// but the demo intentionally uses the O(n^2) helper anyway (see db.js).
|
|
40
|
+
const sorted = db.bubbleSortById(results);
|
|
41
|
+
results = sorted.slice(off, Number.isFinite(lim) ? off + lim : undefined);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return { notes: results };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// GET /notes/count
|
|
48
|
+
// Returns { count } for notes matching optional ?owner= / ?secret= filters.
|
|
49
|
+
//
|
|
50
|
+
// >>> DEMO NOTE: builds its where-clause with naive string concatenation and
|
|
51
|
+
// >>> reuses the vulnerable db.query() eval path — the same injection class
|
|
52
|
+
// >>> issue #2 asks you to remove from listNotes, but reintroduced here in a
|
|
53
|
+
// >>> sibling endpoint issue #2's scope doesn't touch. INTENTIONAL, isolated
|
|
54
|
+
// >>> flaw kept for the benchmark corpus.
|
|
55
|
+
function countNotes(req, res, params) {
|
|
56
|
+
const { owner, secret } = params.query;
|
|
57
|
+
const clauses = [];
|
|
58
|
+
if (owner) clauses.push('owner === "' + owner + '"');
|
|
59
|
+
if (secret !== undefined) clauses.push('secret === ' + (secret === 'true'));
|
|
60
|
+
const whereClause = clauses.length ? clauses.join(' && ') : 'true';
|
|
61
|
+
return { count: db.query(whereClause).length };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// GET /notes/tags
|
|
65
|
+
// Returns the de-duplicated list of tags across all notes.
|
|
66
|
+
//
|
|
67
|
+
// >>> DEMO NOTE: de-dupes with a nested loop (checks every tag against every
|
|
68
|
+
// >>> unique tag seen so far) instead of a Set — O(n^2) on the total tag
|
|
69
|
+
// >>> count. INTENTIONAL, isolated inefficiency kept for the perf corpus.
|
|
70
|
+
function listTags(req, res, params) {
|
|
71
|
+
const unique = [];
|
|
72
|
+
for (const note of db.all()) {
|
|
73
|
+
for (const t of note.tags || []) {
|
|
74
|
+
let seen = false;
|
|
75
|
+
for (const u of unique) {
|
|
76
|
+
if (u === t) {
|
|
77
|
+
seen = true;
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
if (!seen) unique.push(t);
|
|
82
|
+
}
|
|
20
83
|
}
|
|
21
|
-
return {
|
|
84
|
+
return { tags: unique };
|
|
22
85
|
}
|
|
23
86
|
|
|
24
87
|
// GET /notes/:id
|
|
@@ -33,19 +96,53 @@ function getNote(req, res, params) {
|
|
|
33
96
|
}
|
|
34
97
|
|
|
35
98
|
// POST /notes (protected)
|
|
99
|
+
//
|
|
100
|
+
// >>> DEMO NOTE: accepts a `tags` field but never checks that it's an array
|
|
101
|
+
// >>> before storing it. A malformed value (e.g. a number) is stored as-is
|
|
102
|
+
// >>> and later crashes GET /notes?tag= (see listNotes). INTENTIONAL,
|
|
103
|
+
// >>> isolated flaw kept for the benchmark corpus.
|
|
36
104
|
function createNote(req, res, params) {
|
|
37
105
|
requireToken(req);
|
|
38
|
-
const { title, body, owner } = params.body || {};
|
|
106
|
+
const { title, body, owner, tags } = params.body || {};
|
|
39
107
|
if (!title) {
|
|
40
108
|
const err = new Error('title is required');
|
|
41
109
|
err.statusCode = 400;
|
|
42
110
|
throw err;
|
|
43
111
|
}
|
|
44
|
-
const
|
|
112
|
+
const payload = { title, body: body || '', owner: owner || 'anonymous' };
|
|
113
|
+
if (tags !== undefined) payload.tags = tags;
|
|
114
|
+
const note = db.insert(payload);
|
|
45
115
|
res.statusCode = 201;
|
|
46
116
|
return { note };
|
|
47
117
|
}
|
|
48
118
|
|
|
119
|
+
// PATCH /notes/:id (protected)
|
|
120
|
+
//
|
|
121
|
+
// >>> DEMO NOTE: applies `owner` and `secret` from the request body
|
|
122
|
+
// >>> unconditionally alongside `title`/`body`. Any caller holding the
|
|
123
|
+
// >>> shared demo token can reassign a note's ownership or flip its
|
|
124
|
+
// >>> `secret` flag to false, exposing it via the unauthenticated
|
|
125
|
+
// >>> GET /notes listing. This is a mass-assignment / over-permissive-write
|
|
126
|
+
// >>> flaw, distinct from issue #1's missing-auth-check pattern.
|
|
127
|
+
// >>> INTENTIONAL, isolated flaw kept for the benchmark corpus. Note that
|
|
128
|
+
// >>> `tags` is NOT among the fields this handler updates yet — that gap is
|
|
129
|
+
// >>> a separate, not-yet-built feature.
|
|
130
|
+
function updateNote(req, res, params) {
|
|
131
|
+
requireToken(req);
|
|
132
|
+
const note = db.findById(params.id);
|
|
133
|
+
if (!note) {
|
|
134
|
+
const err = new Error('Note not found');
|
|
135
|
+
err.statusCode = 404;
|
|
136
|
+
throw err;
|
|
137
|
+
}
|
|
138
|
+
const { title, body, owner, secret } = params.body || {};
|
|
139
|
+
if (title !== undefined) note.title = title;
|
|
140
|
+
if (body !== undefined) note.body = body;
|
|
141
|
+
if (owner !== undefined) note.owner = owner;
|
|
142
|
+
if (secret !== undefined) note.secret = secret;
|
|
143
|
+
return { note };
|
|
144
|
+
}
|
|
145
|
+
|
|
49
146
|
// DELETE /notes/:id
|
|
50
147
|
//
|
|
51
148
|
// >>> DEMO NOTE: this mutating route is MISSING a requireToken(req) call, so
|
|
@@ -61,4 +158,4 @@ function deleteNote(req, res, params) {
|
|
|
61
158
|
return { deleted: Number(params.id) };
|
|
62
159
|
}
|
|
63
160
|
|
|
64
|
-
module.exports = { listNotes, getNote, createNote, deleteNote };
|
|
161
|
+
module.exports = { listNotes, countNotes, listTags, getNote, createNote, updateNote, deleteNote };
|
|
@@ -14,7 +14,10 @@ const PORT = process.env.PORT || 3000;
|
|
|
14
14
|
const TABLE = [
|
|
15
15
|
['GET', /^\/notes$/, routes.listNotes],
|
|
16
16
|
['POST', /^\/notes$/, routes.createNote],
|
|
17
|
+
['GET', /^\/notes\/count$/, routes.countNotes],
|
|
18
|
+
['GET', /^\/notes\/tags$/, routes.listTags],
|
|
17
19
|
['GET', /^\/notes\/(?<id>\d+)$/, routes.getNote],
|
|
20
|
+
['PATCH', /^\/notes\/(?<id>\d+)$/, routes.updateNote],
|
|
18
21
|
['DELETE', /^\/notes\/(?<id>\d+)$/, routes.deleteNote],
|
|
19
22
|
];
|
|
20
23
|
|
|
@@ -58,7 +61,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
58
61
|
|
|
59
62
|
const [, pattern, handler] = match;
|
|
60
63
|
const groups = url.pathname.match(pattern).groups || {};
|
|
61
|
-
const body = req.method === 'POST' ? await readBody(req) : {};
|
|
64
|
+
const body = req.method === 'POST' || req.method === 'PATCH' ? await readBody(req) : {};
|
|
62
65
|
|
|
63
66
|
try {
|
|
64
67
|
const result = handler(req, res, { ...groups, query, body });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "forgedock",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.2",
|
|
4
4
|
"description": "Autonomous dev pipeline for Claude Code. GitHub as institutional memory — every issue, PR, and annotation survives context resets and makes the next agent smarter.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"forgedock": "bin/forgedock.mjs"
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"type": "git",
|
|
28
28
|
"url": "git+https://github.com/RapierCraftStudios/ForgeDock.git"
|
|
29
29
|
},
|
|
30
|
-
"homepage": "https://
|
|
30
|
+
"homepage": "https://github.com/RapierCraftStudios/ForgeDock#readme",
|
|
31
31
|
"bugs": {
|
|
32
32
|
"url": "https://github.com/RapierCraftStudios/ForgeDock/issues"
|
|
33
33
|
},
|
|
@@ -36,20 +36,28 @@
|
|
|
36
36
|
"!bin/tests/",
|
|
37
37
|
"commands/",
|
|
38
38
|
"docs/",
|
|
39
|
+
"!docs/site/.vitepress/",
|
|
39
40
|
"examples/",
|
|
41
|
+
"packages/protocol/src/",
|
|
40
42
|
"scripts/",
|
|
41
43
|
"templates/",
|
|
42
44
|
"LICENSE",
|
|
43
45
|
"README.md"
|
|
44
46
|
],
|
|
45
47
|
"scripts": {
|
|
46
|
-
"test": "node --test \"bin/tests/**/*.test.mjs\""
|
|
48
|
+
"test": "node --test \"bin/tests/**/*.test.mjs\"",
|
|
49
|
+
"docs:dev": "vitepress dev docs/site",
|
|
50
|
+
"docs:build": "vitepress build docs/site",
|
|
51
|
+
"docs:preview": "vitepress preview docs/site"
|
|
47
52
|
},
|
|
48
53
|
"engines": {
|
|
49
54
|
"node": ">=18"
|
|
50
55
|
},
|
|
51
56
|
"optionalDependencies": {
|
|
52
|
-
"@anthropic-ai/sdk": ">=0.30.0"
|
|
57
|
+
"@anthropic-ai/sdk": ">=0.30.0 <1"
|
|
58
|
+
},
|
|
59
|
+
"devDependencies": {
|
|
60
|
+
"vitepress": "^1.6.3"
|
|
53
61
|
},
|
|
54
62
|
"peerDependencies": {
|
|
55
63
|
"@anthropic-ai/claude-code": ">=2.0.0"
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 RapierCraft Studios
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|