@nebutra/idea-plaza 0.1.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.
- package/.nebutra/debug/content-store.jsonl +5 -0
- package/.nebutra/debug/event-log.jsonl +2 -0
- package/.nebutra/debug/idea-plaza.jsonl +3 -0
- package/.turbo/turbo-build.log +22 -0
- package/.turbo/turbo-test.log +16 -0
- package/.turbo/turbo-typecheck.log +4 -0
- package/CHANGELOG.md +8 -0
- package/LICENSE +676 -0
- package/README.md +27 -0
- package/dist/chunk-2WSISCFT.js +147 -0
- package/dist/chunk-2WSISCFT.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +44 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +80 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/examples/doctor.ts +9 -0
- package/examples/fork.ts +19 -0
- package/examples/quickstart.ts +16 -0
- package/package.json +64 -0
- package/plays/publish_idea/SKILL.md +23 -0
- package/src/cli.ts +36 -0
- package/src/index.test.ts +73 -0
- package/src/index.ts +235 -0
- package/tsconfig.json +9 -0
- package/tsup.config.ts +9 -0
package/README.md
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# @nebutra/idea-plaza
|
|
2
|
+
|
|
3
|
+
Status: **WIP**
|
|
4
|
+
|
|
5
|
+
Opt-in idea publishing and fork lineage for Nebutra ecosystem artifacts.
|
|
6
|
+
|
|
7
|
+
## Boundary
|
|
8
|
+
|
|
9
|
+
Ideas are private by default. This package writes explicit public snapshots and
|
|
10
|
+
fork metadata; it does not expose private founder data or own a global registry
|
|
11
|
+
transport.
|
|
12
|
+
|
|
13
|
+
## Commands
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pnpm idea-plaza:doctor
|
|
17
|
+
pnpm idea-plaza:quickstart
|
|
18
|
+
pnpm idea-plaza:debug
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Current Closure
|
|
22
|
+
|
|
23
|
+
- Sensitive-field scan before publish.
|
|
24
|
+
- Surface/detail/cloneable publication levels.
|
|
25
|
+
- Immutable local snapshot artifacts.
|
|
26
|
+
- Fork attribution always enabled.
|
|
27
|
+
- Cemetery warning metadata on cards.
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import { mkdir } from "fs/promises";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
import { appendCapabilityDebug, readCapabilityDebug } from "@nebutra/capability-kit/debug";
|
|
5
|
+
import { ContentStore } from "@nebutra/content-store";
|
|
6
|
+
import { assertPublicDisclosureSafe } from "@nebutra/ecosystem-safety";
|
|
7
|
+
import { CapabilityError } from "@nebutra/errors";
|
|
8
|
+
import { EventLog } from "@nebutra/event-log";
|
|
9
|
+
import { assetId } from "@nebutra/generation-context";
|
|
10
|
+
import { scanForSensitiveFields } from "@nebutra/ecosystem-safety";
|
|
11
|
+
function requireTenant(value) {
|
|
12
|
+
if (!value?.trim()) {
|
|
13
|
+
throw new CapabilityError("idea-plaza", "Idea Plaza requires tenant context", {
|
|
14
|
+
suggestion: "Pass tenantId so publish snapshots stay tenant-scoped.",
|
|
15
|
+
statusCode: 400
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
return value;
|
|
19
|
+
}
|
|
20
|
+
function bodyForScan(request) {
|
|
21
|
+
return [request.title, request.oneLine, request.body ?? "", request.tags.join(" ")].join("\n");
|
|
22
|
+
}
|
|
23
|
+
var IdeaPlaza = class _IdeaPlaza {
|
|
24
|
+
#tenantId;
|
|
25
|
+
#debugRoot;
|
|
26
|
+
#contentStore;
|
|
27
|
+
#eventLog;
|
|
28
|
+
#ideas = /* @__PURE__ */ new Map();
|
|
29
|
+
#forks = /* @__PURE__ */ new Map();
|
|
30
|
+
constructor(options) {
|
|
31
|
+
this.#tenantId = requireTenant(options.tenantId);
|
|
32
|
+
this.#debugRoot = options.debugRoot ?? process.cwd();
|
|
33
|
+
this.#contentStore = options.contentStore;
|
|
34
|
+
this.#eventLog = options.eventLog;
|
|
35
|
+
}
|
|
36
|
+
static async open(root = ".nebutra/idea-plaza", options = {}) {
|
|
37
|
+
const tenantId = options.tenantId ?? "local";
|
|
38
|
+
await mkdir(root, { recursive: true });
|
|
39
|
+
const contentStore = await ContentStore.open(join(root, "content"), { tenantId });
|
|
40
|
+
const eventLog = await EventLog.open(join(root, "event-log"), { tenantId });
|
|
41
|
+
return new _IdeaPlaza({ ...options, tenantId, contentStore, eventLog });
|
|
42
|
+
}
|
|
43
|
+
async publish(request) {
|
|
44
|
+
assertPublicDisclosureSafe({
|
|
45
|
+
capability: "idea-plaza",
|
|
46
|
+
content: bodyForScan(request),
|
|
47
|
+
...request.redactions !== void 0 ? { redactions: request.redactions } : {},
|
|
48
|
+
suggestion: "Review detected private values and pass redactions before publishing."
|
|
49
|
+
});
|
|
50
|
+
const ideaId = assetId("idea", `${request.title}_${request.oneLine}`);
|
|
51
|
+
const publicationPath = `plaza/ideas/${ideaId}.json`;
|
|
52
|
+
const publishedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
53
|
+
const idea = {
|
|
54
|
+
ideaId,
|
|
55
|
+
title: request.title,
|
|
56
|
+
oneLine: request.oneLine,
|
|
57
|
+
level: request.level,
|
|
58
|
+
tags: request.tags,
|
|
59
|
+
redactions: request.redactions ?? [],
|
|
60
|
+
cemeteryWarnings: request.cemeteryWarnings ?? [],
|
|
61
|
+
publicationPath,
|
|
62
|
+
publishedAt
|
|
63
|
+
};
|
|
64
|
+
const snapshot = `${JSON.stringify({ ...idea, body: request.body ?? "" }, null, 2)}
|
|
65
|
+
`;
|
|
66
|
+
await this.#contentStore.write(publicationPath, snapshot);
|
|
67
|
+
const eventId = await this.#eventLog.commit({
|
|
68
|
+
traceId: assetId("idea_plaza_publish", ideaId),
|
|
69
|
+
kind: "content_write",
|
|
70
|
+
affected: [publicationPath],
|
|
71
|
+
parent: null,
|
|
72
|
+
snapshot: { [publicationPath]: snapshot }
|
|
73
|
+
});
|
|
74
|
+
const published = { ...idea, eventId };
|
|
75
|
+
this.#ideas.set(ideaId, published);
|
|
76
|
+
await this.#debug({ type: "publish", tenantId: this.#tenantId, ideaId, level: request.level });
|
|
77
|
+
return published;
|
|
78
|
+
}
|
|
79
|
+
async fork(ideaId, request) {
|
|
80
|
+
if (!this.#ideas.has(ideaId)) {
|
|
81
|
+
throw new CapabilityError("idea-plaza", "Idea fork source not found", {
|
|
82
|
+
suggestion: "Fork only from an explicit public or cloneable snapshot in the current feed.",
|
|
83
|
+
metadata: { ideaId },
|
|
84
|
+
statusCode: 404
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
const fork = {
|
|
88
|
+
forkId: assetId("idea_fork", `${ideaId}_${request.newFounderId}`),
|
|
89
|
+
sourceIdeaId: ideaId,
|
|
90
|
+
newFounderId: request.newFounderId,
|
|
91
|
+
inheritPlayPreferences: request.inheritPlayPreferences ?? false,
|
|
92
|
+
attribution: {
|
|
93
|
+
displayInNewCompany: true,
|
|
94
|
+
publicLineage: true
|
|
95
|
+
},
|
|
96
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
97
|
+
};
|
|
98
|
+
const path = `plaza/forks/${fork.forkId}.json`;
|
|
99
|
+
const snapshot = `${JSON.stringify(fork, null, 2)}
|
|
100
|
+
`;
|
|
101
|
+
await this.#contentStore.write(path, snapshot);
|
|
102
|
+
await this.#eventLog.commit({
|
|
103
|
+
traceId: assetId("idea_plaza_fork", fork.forkId),
|
|
104
|
+
kind: "content_write",
|
|
105
|
+
affected: [path],
|
|
106
|
+
parent: null,
|
|
107
|
+
snapshot: { [path]: snapshot }
|
|
108
|
+
});
|
|
109
|
+
this.#forks.set(fork.forkId, fork);
|
|
110
|
+
await this.#debug({ type: "fork", tenantId: this.#tenantId, ideaId, forkId: fork.forkId });
|
|
111
|
+
return fork;
|
|
112
|
+
}
|
|
113
|
+
async feed(request) {
|
|
114
|
+
const items = [...this.#ideas.values()].sort(
|
|
115
|
+
(left, right) => request.sort === "new" ? right.publishedAt.localeCompare(left.publishedAt) : right.cemeteryWarnings.length - left.cemeteryWarnings.length || right.publishedAt.localeCompare(left.publishedAt)
|
|
116
|
+
).slice(0, request.limit);
|
|
117
|
+
await this.#debug({ type: "feed", tenantId: this.#tenantId, sort: request.sort });
|
|
118
|
+
return { items };
|
|
119
|
+
}
|
|
120
|
+
async doctor() {
|
|
121
|
+
const report = {
|
|
122
|
+
capability: "idea-plaza",
|
|
123
|
+
ok: true,
|
|
124
|
+
checkedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
125
|
+
features: ["publish", "redact", "fork-lineage", "feed", "cemetery-warning"],
|
|
126
|
+
suggestion: "Local snapshots are active; public registry transport and moderation queues remain adapter handoffs."
|
|
127
|
+
};
|
|
128
|
+
await this.#debug({ type: "doctor", tenantId: this.#tenantId, ideas: this.#ideas.size });
|
|
129
|
+
return report;
|
|
130
|
+
}
|
|
131
|
+
async close() {
|
|
132
|
+
await this.#contentStore.close();
|
|
133
|
+
}
|
|
134
|
+
async #debug(entry) {
|
|
135
|
+
await appendCapabilityDebug("idea-plaza", entry, { root: this.#debugRoot });
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
async function readIdeaPlazaDebug(root = process.cwd(), limit = 20) {
|
|
139
|
+
return readCapabilityDebug("idea-plaza", { root, limit });
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export {
|
|
143
|
+
IdeaPlaza,
|
|
144
|
+
readIdeaPlazaDebug,
|
|
145
|
+
scanForSensitiveFields
|
|
146
|
+
};
|
|
147
|
+
//# sourceMappingURL=chunk-2WSISCFT.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { mkdir } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { appendCapabilityDebug, readCapabilityDebug } from \"@nebutra/capability-kit/debug\";\nimport { ContentStore } from \"@nebutra/content-store\";\nimport { assertPublicDisclosureSafe } from \"@nebutra/ecosystem-safety\";\nimport { CapabilityError } from \"@nebutra/errors\";\nimport { EventLog } from \"@nebutra/event-log\";\nimport { assetId } from \"@nebutra/generation-context\";\n\nexport type IdeaPublishLevel = \"surface\" | \"detail\" | \"cloneable\";\nexport type FeedSort = \"hot\" | \"new\" | \"near_you\";\nexport type { SensitiveField } from \"@nebutra/ecosystem-safety\";\nexport { scanForSensitiveFields } from \"@nebutra/ecosystem-safety\";\n\nexport interface CemeteryWarning {\n readonly title: string;\n readonly cause: string;\n}\n\nexport interface PublishIdeaRequest {\n readonly title: string;\n readonly oneLine: string;\n readonly level: IdeaPublishLevel;\n readonly body?: string;\n readonly tags: readonly string[];\n readonly redactions?: readonly string[];\n readonly cemeteryWarnings?: readonly CemeteryWarning[];\n}\n\nexport interface PublishedIdea {\n readonly ideaId: string;\n readonly title: string;\n readonly oneLine: string;\n readonly level: IdeaPublishLevel;\n readonly tags: readonly string[];\n readonly redactions: readonly string[];\n readonly cemeteryWarnings: readonly CemeteryWarning[];\n readonly publicationPath: string;\n readonly eventId: string;\n readonly publishedAt: string;\n}\n\nexport interface ForkRequest {\n readonly newFounderId: string;\n readonly inheritPlayPreferences?: boolean;\n}\n\nexport interface IdeaFork {\n readonly forkId: string;\n readonly sourceIdeaId: string;\n readonly newFounderId: string;\n readonly inheritPlayPreferences: boolean;\n readonly attribution: {\n readonly displayInNewCompany: true;\n readonly publicLineage: true;\n };\n readonly createdAt: string;\n}\n\nexport interface FeedRequest {\n readonly sort: FeedSort;\n readonly limit: number;\n}\n\nexport interface IdeaFeed {\n readonly items: readonly PublishedIdea[];\n}\n\nexport interface IdeaPlazaDoctorReport {\n readonly capability: \"idea-plaza\";\n readonly ok: boolean;\n readonly checkedAt: string;\n readonly features: readonly string[];\n readonly suggestion?: string;\n}\n\nexport interface IdeaPlazaOptions {\n readonly tenantId?: string;\n readonly debugRoot?: string;\n readonly contentStore?: ContentStore;\n readonly eventLog?: EventLog;\n}\n\nfunction requireTenant(value: string | undefined): string {\n if (!value?.trim()) {\n throw new CapabilityError(\"idea-plaza\", \"Idea Plaza requires tenant context\", {\n suggestion: \"Pass tenantId so publish snapshots stay tenant-scoped.\",\n statusCode: 400,\n });\n }\n return value;\n}\n\nfunction bodyForScan(request: PublishIdeaRequest): string {\n return [request.title, request.oneLine, request.body ?? \"\", request.tags.join(\" \")].join(\"\\n\");\n}\n\nexport class IdeaPlaza {\n readonly #tenantId: string;\n readonly #debugRoot: string;\n readonly #contentStore: ContentStore;\n readonly #eventLog: EventLog;\n readonly #ideas = new Map<string, PublishedIdea>();\n readonly #forks = new Map<string, IdeaFork>();\n\n private constructor(\n options: Required<Pick<IdeaPlazaOptions, \"tenantId\" | \"contentStore\" | \"eventLog\">> &\n Pick<IdeaPlazaOptions, \"debugRoot\">,\n ) {\n this.#tenantId = requireTenant(options.tenantId);\n this.#debugRoot = options.debugRoot ?? process.cwd();\n this.#contentStore = options.contentStore;\n this.#eventLog = options.eventLog;\n }\n\n static async open(\n root = \".nebutra/idea-plaza\",\n options: Omit<IdeaPlazaOptions, \"contentStore\" | \"eventLog\"> = {},\n ): Promise<IdeaPlaza> {\n const tenantId = options.tenantId ?? \"local\";\n await mkdir(root, { recursive: true });\n const contentStore = await ContentStore.open(join(root, \"content\"), { tenantId });\n const eventLog = await EventLog.open(join(root, \"event-log\"), { tenantId });\n return new IdeaPlaza({ ...options, tenantId, contentStore, eventLog });\n }\n\n async publish(request: PublishIdeaRequest): Promise<PublishedIdea> {\n assertPublicDisclosureSafe({\n capability: \"idea-plaza\",\n content: bodyForScan(request),\n ...(request.redactions !== undefined ? { redactions: request.redactions } : {}),\n suggestion: \"Review detected private values and pass redactions before publishing.\",\n });\n\n const ideaId = assetId(\"idea\", `${request.title}_${request.oneLine}`);\n const publicationPath = `plaza/ideas/${ideaId}.json`;\n const publishedAt = new Date().toISOString();\n const idea: Omit<PublishedIdea, \"eventId\"> = {\n ideaId,\n title: request.title,\n oneLine: request.oneLine,\n level: request.level,\n tags: request.tags,\n redactions: request.redactions ?? [],\n cemeteryWarnings: request.cemeteryWarnings ?? [],\n publicationPath,\n publishedAt,\n };\n const snapshot = `${JSON.stringify({ ...idea, body: request.body ?? \"\" }, null, 2)}\\n`;\n await this.#contentStore.write(publicationPath, snapshot);\n const eventId = await this.#eventLog.commit({\n traceId: assetId(\"idea_plaza_publish\", ideaId),\n kind: \"content_write\",\n affected: [publicationPath],\n parent: null,\n snapshot: { [publicationPath]: snapshot },\n });\n const published = { ...idea, eventId };\n this.#ideas.set(ideaId, published);\n await this.#debug({ type: \"publish\", tenantId: this.#tenantId, ideaId, level: request.level });\n return published;\n }\n\n async fork(ideaId: string, request: ForkRequest): Promise<IdeaFork> {\n if (!this.#ideas.has(ideaId)) {\n throw new CapabilityError(\"idea-plaza\", \"Idea fork source not found\", {\n suggestion: \"Fork only from an explicit public or cloneable snapshot in the current feed.\",\n metadata: { ideaId },\n statusCode: 404,\n });\n }\n const fork: IdeaFork = {\n forkId: assetId(\"idea_fork\", `${ideaId}_${request.newFounderId}`),\n sourceIdeaId: ideaId,\n newFounderId: request.newFounderId,\n inheritPlayPreferences: request.inheritPlayPreferences ?? false,\n attribution: {\n displayInNewCompany: true,\n publicLineage: true,\n },\n createdAt: new Date().toISOString(),\n };\n const path = `plaza/forks/${fork.forkId}.json`;\n const snapshot = `${JSON.stringify(fork, null, 2)}\\n`;\n await this.#contentStore.write(path, snapshot);\n await this.#eventLog.commit({\n traceId: assetId(\"idea_plaza_fork\", fork.forkId),\n kind: \"content_write\",\n affected: [path],\n parent: null,\n snapshot: { [path]: snapshot },\n });\n this.#forks.set(fork.forkId, fork);\n await this.#debug({ type: \"fork\", tenantId: this.#tenantId, ideaId, forkId: fork.forkId });\n return fork;\n }\n\n async feed(request: FeedRequest): Promise<IdeaFeed> {\n const items = [...this.#ideas.values()]\n .sort((left, right) =>\n request.sort === \"new\"\n ? right.publishedAt.localeCompare(left.publishedAt)\n : right.cemeteryWarnings.length - left.cemeteryWarnings.length ||\n right.publishedAt.localeCompare(left.publishedAt),\n )\n .slice(0, request.limit);\n await this.#debug({ type: \"feed\", tenantId: this.#tenantId, sort: request.sort });\n return { items };\n }\n\n async doctor(): Promise<IdeaPlazaDoctorReport> {\n const report: IdeaPlazaDoctorReport = {\n capability: \"idea-plaza\",\n ok: true,\n checkedAt: new Date().toISOString(),\n features: [\"publish\", \"redact\", \"fork-lineage\", \"feed\", \"cemetery-warning\"],\n suggestion:\n \"Local snapshots are active; public registry transport and moderation queues remain adapter handoffs.\",\n };\n await this.#debug({ type: \"doctor\", tenantId: this.#tenantId, ideas: this.#ideas.size });\n return report;\n }\n\n async close(): Promise<void> {\n await this.#contentStore.close();\n }\n\n async #debug(entry: Record<string, unknown>): Promise<void> {\n await appendCapabilityDebug(\"idea-plaza\", entry, { root: this.#debugRoot });\n }\n}\n\nexport async function readIdeaPlazaDebug(root = process.cwd(), limit = 20): Promise<unknown[]> {\n return readCapabilityDebug(\"idea-plaza\", { root, limit });\n}\n"],"mappings":"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","names":[]}
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import {
|
|
2
|
+
IdeaPlaza,
|
|
3
|
+
readIdeaPlazaDebug
|
|
4
|
+
} from "./chunk-2WSISCFT.js";
|
|
5
|
+
|
|
6
|
+
// src/cli.ts
|
|
7
|
+
var command = process.argv[2] ?? "doctor";
|
|
8
|
+
var root = process.env.IDEA_PLAZA_ROOT ?? ".nebutra/idea-plaza";
|
|
9
|
+
var tenantId = process.env.NEBUTRA_TENANT_ID ?? "local";
|
|
10
|
+
if (command === "doctor") {
|
|
11
|
+
const plaza = await IdeaPlaza.open(root, { tenantId });
|
|
12
|
+
try {
|
|
13
|
+
process.stdout.write(`${JSON.stringify(await plaza.doctor(), null, 2)}
|
|
14
|
+
`);
|
|
15
|
+
} finally {
|
|
16
|
+
await plaza.close();
|
|
17
|
+
}
|
|
18
|
+
} else if (command === "quickstart") {
|
|
19
|
+
const plaza = await IdeaPlaza.open(root, { tenantId });
|
|
20
|
+
try {
|
|
21
|
+
const idea = await plaza.publish({
|
|
22
|
+
title: "Loop",
|
|
23
|
+
oneLine: "AI debugging for indie devs",
|
|
24
|
+
level: "surface",
|
|
25
|
+
tags: ["debugging", "developer-tools"]
|
|
26
|
+
});
|
|
27
|
+
process.stdout.write(
|
|
28
|
+
`${JSON.stringify({ idea, feed: await plaza.feed({ sort: "hot", limit: 5 }) }, null, 2)}
|
|
29
|
+
`
|
|
30
|
+
);
|
|
31
|
+
} finally {
|
|
32
|
+
await plaza.close();
|
|
33
|
+
}
|
|
34
|
+
} else if (command === "debug") {
|
|
35
|
+
process.stdout.write(
|
|
36
|
+
`${JSON.stringify({ capability: "idea-plaza", entries: await readIdeaPlazaDebug() }, null, 2)}
|
|
37
|
+
`
|
|
38
|
+
);
|
|
39
|
+
} else {
|
|
40
|
+
process.stderr.write(`Unknown idea-plaza command: ${command}
|
|
41
|
+
`);
|
|
42
|
+
process.exitCode = 1;
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts"],"sourcesContent":["import { IdeaPlaza, readIdeaPlazaDebug } from \"./index\";\n\nconst command = process.argv[2] ?? \"doctor\";\nconst root = process.env.IDEA_PLAZA_ROOT ?? \".nebutra/idea-plaza\";\nconst tenantId = process.env.NEBUTRA_TENANT_ID ?? \"local\";\n\nif (command === \"doctor\") {\n const plaza = await IdeaPlaza.open(root, { tenantId });\n try {\n process.stdout.write(`${JSON.stringify(await plaza.doctor(), null, 2)}\\n`);\n } finally {\n await plaza.close();\n }\n} else if (command === \"quickstart\") {\n const plaza = await IdeaPlaza.open(root, { tenantId });\n try {\n const idea = await plaza.publish({\n title: \"Loop\",\n oneLine: \"AI debugging for indie devs\",\n level: \"surface\",\n tags: [\"debugging\", \"developer-tools\"],\n });\n process.stdout.write(\n `${JSON.stringify({ idea, feed: await plaza.feed({ sort: \"hot\", limit: 5 }) }, null, 2)}\\n`,\n );\n } finally {\n await plaza.close();\n }\n} else if (command === \"debug\") {\n process.stdout.write(\n `${JSON.stringify({ capability: \"idea-plaza\", entries: await readIdeaPlazaDebug() }, null, 2)}\\n`,\n );\n} else {\n process.stderr.write(`Unknown idea-plaza command: ${command}\\n`);\n process.exitCode = 1;\n}\n"],"mappings":";;;;;;AAEA,IAAM,UAAU,QAAQ,KAAK,CAAC,KAAK;AACnC,IAAM,OAAO,QAAQ,IAAI,mBAAmB;AAC5C,IAAM,WAAW,QAAQ,IAAI,qBAAqB;AAElD,IAAI,YAAY,UAAU;AACxB,QAAM,QAAQ,MAAM,UAAU,KAAK,MAAM,EAAE,SAAS,CAAC;AACrD,MAAI;AACF,YAAQ,OAAO,MAAM,GAAG,KAAK,UAAU,MAAM,MAAM,OAAO,GAAG,MAAM,CAAC,CAAC;AAAA,CAAI;AAAA,EAC3E,UAAE;AACA,UAAM,MAAM,MAAM;AAAA,EACpB;AACF,WAAW,YAAY,cAAc;AACnC,QAAM,QAAQ,MAAM,UAAU,KAAK,MAAM,EAAE,SAAS,CAAC;AACrD,MAAI;AACF,UAAM,OAAO,MAAM,MAAM,QAAQ;AAAA,MAC/B,OAAO;AAAA,MACP,SAAS;AAAA,MACT,OAAO;AAAA,MACP,MAAM,CAAC,aAAa,iBAAiB;AAAA,IACvC,CAAC;AACD,YAAQ,OAAO;AAAA,MACb,GAAG,KAAK,UAAU,EAAE,MAAM,MAAM,MAAM,MAAM,KAAK,EAAE,MAAM,OAAO,OAAO,EAAE,CAAC,EAAE,GAAG,MAAM,CAAC,CAAC;AAAA;AAAA,IACzF;AAAA,EACF,UAAE;AACA,UAAM,MAAM,MAAM;AAAA,EACpB;AACF,WAAW,YAAY,SAAS;AAC9B,UAAQ,OAAO;AAAA,IACb,GAAG,KAAK,UAAU,EAAE,YAAY,cAAc,SAAS,MAAM,mBAAmB,EAAE,GAAG,MAAM,CAAC,CAAC;AAAA;AAAA,EAC/F;AACF,OAAO;AACL,UAAQ,OAAO,MAAM,+BAA+B,OAAO;AAAA,CAAI;AAC/D,UAAQ,WAAW;AACrB;","names":[]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { ContentStore } from '@nebutra/content-store';
|
|
2
|
+
import { EventLog } from '@nebutra/event-log';
|
|
3
|
+
export { SensitiveField, scanForSensitiveFields } from '@nebutra/ecosystem-safety';
|
|
4
|
+
|
|
5
|
+
type IdeaPublishLevel = "surface" | "detail" | "cloneable";
|
|
6
|
+
type FeedSort = "hot" | "new" | "near_you";
|
|
7
|
+
|
|
8
|
+
interface CemeteryWarning {
|
|
9
|
+
readonly title: string;
|
|
10
|
+
readonly cause: string;
|
|
11
|
+
}
|
|
12
|
+
interface PublishIdeaRequest {
|
|
13
|
+
readonly title: string;
|
|
14
|
+
readonly oneLine: string;
|
|
15
|
+
readonly level: IdeaPublishLevel;
|
|
16
|
+
readonly body?: string;
|
|
17
|
+
readonly tags: readonly string[];
|
|
18
|
+
readonly redactions?: readonly string[];
|
|
19
|
+
readonly cemeteryWarnings?: readonly CemeteryWarning[];
|
|
20
|
+
}
|
|
21
|
+
interface PublishedIdea {
|
|
22
|
+
readonly ideaId: string;
|
|
23
|
+
readonly title: string;
|
|
24
|
+
readonly oneLine: string;
|
|
25
|
+
readonly level: IdeaPublishLevel;
|
|
26
|
+
readonly tags: readonly string[];
|
|
27
|
+
readonly redactions: readonly string[];
|
|
28
|
+
readonly cemeteryWarnings: readonly CemeteryWarning[];
|
|
29
|
+
readonly publicationPath: string;
|
|
30
|
+
readonly eventId: string;
|
|
31
|
+
readonly publishedAt: string;
|
|
32
|
+
}
|
|
33
|
+
interface ForkRequest {
|
|
34
|
+
readonly newFounderId: string;
|
|
35
|
+
readonly inheritPlayPreferences?: boolean;
|
|
36
|
+
}
|
|
37
|
+
interface IdeaFork {
|
|
38
|
+
readonly forkId: string;
|
|
39
|
+
readonly sourceIdeaId: string;
|
|
40
|
+
readonly newFounderId: string;
|
|
41
|
+
readonly inheritPlayPreferences: boolean;
|
|
42
|
+
readonly attribution: {
|
|
43
|
+
readonly displayInNewCompany: true;
|
|
44
|
+
readonly publicLineage: true;
|
|
45
|
+
};
|
|
46
|
+
readonly createdAt: string;
|
|
47
|
+
}
|
|
48
|
+
interface FeedRequest {
|
|
49
|
+
readonly sort: FeedSort;
|
|
50
|
+
readonly limit: number;
|
|
51
|
+
}
|
|
52
|
+
interface IdeaFeed {
|
|
53
|
+
readonly items: readonly PublishedIdea[];
|
|
54
|
+
}
|
|
55
|
+
interface IdeaPlazaDoctorReport {
|
|
56
|
+
readonly capability: "idea-plaza";
|
|
57
|
+
readonly ok: boolean;
|
|
58
|
+
readonly checkedAt: string;
|
|
59
|
+
readonly features: readonly string[];
|
|
60
|
+
readonly suggestion?: string;
|
|
61
|
+
}
|
|
62
|
+
interface IdeaPlazaOptions {
|
|
63
|
+
readonly tenantId?: string;
|
|
64
|
+
readonly debugRoot?: string;
|
|
65
|
+
readonly contentStore?: ContentStore;
|
|
66
|
+
readonly eventLog?: EventLog;
|
|
67
|
+
}
|
|
68
|
+
declare class IdeaPlaza {
|
|
69
|
+
#private;
|
|
70
|
+
private constructor();
|
|
71
|
+
static open(root?: string, options?: Omit<IdeaPlazaOptions, "contentStore" | "eventLog">): Promise<IdeaPlaza>;
|
|
72
|
+
publish(request: PublishIdeaRequest): Promise<PublishedIdea>;
|
|
73
|
+
fork(ideaId: string, request: ForkRequest): Promise<IdeaFork>;
|
|
74
|
+
feed(request: FeedRequest): Promise<IdeaFeed>;
|
|
75
|
+
doctor(): Promise<IdeaPlazaDoctorReport>;
|
|
76
|
+
close(): Promise<void>;
|
|
77
|
+
}
|
|
78
|
+
declare function readIdeaPlazaDebug(root?: string, limit?: number): Promise<unknown[]>;
|
|
79
|
+
|
|
80
|
+
export { type CemeteryWarning, type FeedRequest, type FeedSort, type ForkRequest, type IdeaFeed, type IdeaFork, IdeaPlaza, type IdeaPlazaDoctorReport, type IdeaPlazaOptions, type IdeaPublishLevel, type PublishIdeaRequest, type PublishedIdea, readIdeaPlazaDebug };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { IdeaPlaza } from "../src/index";
|
|
2
|
+
|
|
3
|
+
const plaza = await IdeaPlaza.open(".nebutra/idea-plaza-example", { tenantId: "local" });
|
|
4
|
+
|
|
5
|
+
try {
|
|
6
|
+
process.stdout.write(`${JSON.stringify(await plaza.doctor(), null, 2)}\n`);
|
|
7
|
+
} finally {
|
|
8
|
+
await plaza.close();
|
|
9
|
+
}
|
package/examples/fork.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { IdeaPlaza } from "../src/index";
|
|
2
|
+
|
|
3
|
+
const plaza = await IdeaPlaza.open(".nebutra/idea-plaza-fork-example", { tenantId: "local" });
|
|
4
|
+
|
|
5
|
+
try {
|
|
6
|
+
const idea = await plaza.publish({
|
|
7
|
+
title: "Loop",
|
|
8
|
+
oneLine: "AI debugging for indie devs",
|
|
9
|
+
level: "cloneable",
|
|
10
|
+
tags: ["debugging"],
|
|
11
|
+
});
|
|
12
|
+
const fork = await plaza.fork(idea.ideaId, {
|
|
13
|
+
newFounderId: "bob",
|
|
14
|
+
inheritPlayPreferences: true,
|
|
15
|
+
});
|
|
16
|
+
process.stdout.write(`${JSON.stringify(fork, null, 2)}\n`);
|
|
17
|
+
} finally {
|
|
18
|
+
await plaza.close();
|
|
19
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { IdeaPlaza } from "../src/index";
|
|
2
|
+
|
|
3
|
+
const plaza = await IdeaPlaza.open(".nebutra/idea-plaza-example", { tenantId: "local" });
|
|
4
|
+
|
|
5
|
+
try {
|
|
6
|
+
const idea = await plaza.publish({
|
|
7
|
+
title: "Loop",
|
|
8
|
+
oneLine: "AI debugging for indie devs",
|
|
9
|
+
level: "surface",
|
|
10
|
+
body: "A developer tool that explains stack traces as a conversation.",
|
|
11
|
+
tags: ["debugging", "developer-tools"],
|
|
12
|
+
});
|
|
13
|
+
process.stdout.write(`${JSON.stringify(idea, null, 2)}\n`);
|
|
14
|
+
} finally {
|
|
15
|
+
await plaza.close();
|
|
16
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@nebutra/idea-plaza",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Explicit opt-in idea publishing, lineage, and fork surface over Nebutra company artifacts",
|
|
5
|
+
"private": false,
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"nebutra": {
|
|
9
|
+
"status": "wip",
|
|
10
|
+
"productionReady": false,
|
|
11
|
+
"surface": "ecosystem-product",
|
|
12
|
+
"requires": [
|
|
13
|
+
"@nebutra/content-store for public snapshots",
|
|
14
|
+
"@nebutra/event-log for publish and fork checkpoints",
|
|
15
|
+
"@nebutra/ecosystem-safety for public-disclosure PII and secret checks",
|
|
16
|
+
"@nebutra/capability-kit for debug and doctor surfaces"
|
|
17
|
+
],
|
|
18
|
+
"gaps": [
|
|
19
|
+
"Public registry transport is represented as local immutable snapshots",
|
|
20
|
+
"Moderation is deterministic metadata until human review queues exist",
|
|
21
|
+
"Cemetery similarity warnings are provided by input contracts until global KB is online"
|
|
22
|
+
],
|
|
23
|
+
"featureId": "idea-plaza",
|
|
24
|
+
"category": "ai",
|
|
25
|
+
"summary": "Opt-in public idea snapshot and fork lineage product surface"
|
|
26
|
+
},
|
|
27
|
+
"main": "./src/index.ts",
|
|
28
|
+
"types": "./src/index.ts",
|
|
29
|
+
"exports": {
|
|
30
|
+
".": "./src/index.ts"
|
|
31
|
+
},
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"@nebutra/capability-kit": "0.2.0",
|
|
34
|
+
"@nebutra/content-store": "0.1.0",
|
|
35
|
+
"@nebutra/ecosystem-safety": "0.1.0",
|
|
36
|
+
"@nebutra/errors": "0.1.0",
|
|
37
|
+
"@nebutra/event-log": "0.1.0",
|
|
38
|
+
"@nebutra/generation-context": "0.1.0"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@types/node": "^22.19.15",
|
|
42
|
+
"tsup": "^8.5.1",
|
|
43
|
+
"tsx": "^4.21.0",
|
|
44
|
+
"typescript": "^5.9.3",
|
|
45
|
+
"vitest": "^4.1.4"
|
|
46
|
+
},
|
|
47
|
+
"homepage": "https://github.com/Nebutra/Nebutra-Sailor/tree/main/packages/ai/idea-plaza#readme",
|
|
48
|
+
"repository": {
|
|
49
|
+
"type": "git",
|
|
50
|
+
"url": "git+https://github.com/Nebutra/Nebutra-Sailor.git",
|
|
51
|
+
"directory": "packages/ai/idea-plaza"
|
|
52
|
+
},
|
|
53
|
+
"bugs": {
|
|
54
|
+
"url": "https://github.com/Nebutra/Nebutra-Sailor/issues"
|
|
55
|
+
},
|
|
56
|
+
"publishConfig": {
|
|
57
|
+
"access": "public"
|
|
58
|
+
},
|
|
59
|
+
"scripts": {
|
|
60
|
+
"build": "tsup",
|
|
61
|
+
"test": "vitest run",
|
|
62
|
+
"typecheck": "tsc --noEmit"
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: publish_idea
|
|
3
|
+
kind: play
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
description: Publish an explicit, redacted idea snapshot for discovery and fork lineage
|
|
6
|
+
required_skills:
|
|
7
|
+
- content_store.write
|
|
8
|
+
- event_log.commit
|
|
9
|
+
sub_agents:
|
|
10
|
+
- role: privacy_reviewer
|
|
11
|
+
allowed_skills: [content_store.write]
|
|
12
|
+
depends_on_plays: []
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## What this play does
|
|
16
|
+
|
|
17
|
+
Creates an opt-in public snapshot of an idea with lineage metadata.
|
|
18
|
+
|
|
19
|
+
## Boundaries
|
|
20
|
+
|
|
21
|
+
- Default state is private.
|
|
22
|
+
- Any detected sensitive field must be redacted before publishing.
|
|
23
|
+
- Fork attribution remains on so the lineage graph stays trustworthy.
|
package/src/cli.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { IdeaPlaza, readIdeaPlazaDebug } from "./index";
|
|
2
|
+
|
|
3
|
+
const command = process.argv[2] ?? "doctor";
|
|
4
|
+
const root = process.env.IDEA_PLAZA_ROOT ?? ".nebutra/idea-plaza";
|
|
5
|
+
const tenantId = process.env.NEBUTRA_TENANT_ID ?? "local";
|
|
6
|
+
|
|
7
|
+
if (command === "doctor") {
|
|
8
|
+
const plaza = await IdeaPlaza.open(root, { tenantId });
|
|
9
|
+
try {
|
|
10
|
+
process.stdout.write(`${JSON.stringify(await plaza.doctor(), null, 2)}\n`);
|
|
11
|
+
} finally {
|
|
12
|
+
await plaza.close();
|
|
13
|
+
}
|
|
14
|
+
} else if (command === "quickstart") {
|
|
15
|
+
const plaza = await IdeaPlaza.open(root, { tenantId });
|
|
16
|
+
try {
|
|
17
|
+
const idea = await plaza.publish({
|
|
18
|
+
title: "Loop",
|
|
19
|
+
oneLine: "AI debugging for indie devs",
|
|
20
|
+
level: "surface",
|
|
21
|
+
tags: ["debugging", "developer-tools"],
|
|
22
|
+
});
|
|
23
|
+
process.stdout.write(
|
|
24
|
+
`${JSON.stringify({ idea, feed: await plaza.feed({ sort: "hot", limit: 5 }) }, null, 2)}\n`,
|
|
25
|
+
);
|
|
26
|
+
} finally {
|
|
27
|
+
await plaza.close();
|
|
28
|
+
}
|
|
29
|
+
} else if (command === "debug") {
|
|
30
|
+
process.stdout.write(
|
|
31
|
+
`${JSON.stringify({ capability: "idea-plaza", entries: await readIdeaPlazaDebug() }, null, 2)}\n`,
|
|
32
|
+
);
|
|
33
|
+
} else {
|
|
34
|
+
process.stderr.write(`Unknown idea-plaza command: ${command}\n`);
|
|
35
|
+
process.exitCode = 1;
|
|
36
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { mkdtemp, rm } from "node:fs/promises";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { afterEach, describe, expect, it } from "vitest";
|
|
5
|
+
import { IdeaPlaza, readIdeaPlazaDebug, scanForSensitiveFields } from "./index";
|
|
6
|
+
|
|
7
|
+
let root: string | undefined;
|
|
8
|
+
let plaza: IdeaPlaza | undefined;
|
|
9
|
+
|
|
10
|
+
afterEach(async () => {
|
|
11
|
+
if (plaza) await plaza.close();
|
|
12
|
+
if (root) await rm(root, { recursive: true, force: true });
|
|
13
|
+
root = undefined;
|
|
14
|
+
plaza = undefined;
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
async function open(): Promise<IdeaPlaza> {
|
|
18
|
+
root = await mkdtemp(join(tmpdir(), "idea-plaza-"));
|
|
19
|
+
plaza = await IdeaPlaza.open(root, { tenantId: "tenant_a" });
|
|
20
|
+
return plaza;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
describe("idea-plaza", () => {
|
|
24
|
+
it("blocks opt-in publish when sensitive data is not explicitly redacted", async () => {
|
|
25
|
+
const runtime = await open();
|
|
26
|
+
|
|
27
|
+
await expect(
|
|
28
|
+
runtime.publish({
|
|
29
|
+
title: "Loop",
|
|
30
|
+
oneLine: "AI debugging",
|
|
31
|
+
level: "detail",
|
|
32
|
+
body: "Contact alice@example.com with api key sk-test-1234567890",
|
|
33
|
+
tags: ["debugging"],
|
|
34
|
+
}),
|
|
35
|
+
).rejects.toMatchObject({
|
|
36
|
+
capability: "idea-plaza",
|
|
37
|
+
suggestion: expect.stringContaining("redact"),
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("publishes explicit snapshots and preserves fork attribution", async () => {
|
|
42
|
+
const runtime = await open();
|
|
43
|
+
const published = await runtime.publish({
|
|
44
|
+
title: "Loop",
|
|
45
|
+
oneLine: "AI debugging",
|
|
46
|
+
level: "cloneable",
|
|
47
|
+
body: "Developer tool for stack trace conversations.",
|
|
48
|
+
tags: ["debugging", "developer-tools"],
|
|
49
|
+
redactions: ["customers", "finances"],
|
|
50
|
+
cemeteryWarnings: [{ title: "DebugMate", cause: "no PMF" }],
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const fork = await runtime.fork(published.ideaId, {
|
|
54
|
+
newFounderId: "bob",
|
|
55
|
+
inheritPlayPreferences: true,
|
|
56
|
+
});
|
|
57
|
+
const feed = await runtime.feed({ sort: "hot", limit: 10 });
|
|
58
|
+
|
|
59
|
+
expect(published.publicationPath).toBe(`plaza/ideas/${published.ideaId}.json`);
|
|
60
|
+
expect(published.cemeteryWarnings).toHaveLength(1);
|
|
61
|
+
expect(fork.attribution.publicLineage).toBe(true);
|
|
62
|
+
expect(fork.sourceIdeaId).toBe(published.ideaId);
|
|
63
|
+
expect(feed.items.map((item) => item.ideaId)).toContain(published.ideaId);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it("keeps PII detection deterministic and auditable", async () => {
|
|
67
|
+
expect(scanForSensitiveFields("email me at a@example.com")).toEqual([
|
|
68
|
+
{ kind: "email", value: "a@example.com" },
|
|
69
|
+
]);
|
|
70
|
+
await open();
|
|
71
|
+
await expect(readIdeaPlazaDebug(root)).resolves.toEqual(expect.any(Array));
|
|
72
|
+
});
|
|
73
|
+
});
|